feat: isolate security config controls from #27#40
feat: isolate security config controls from #27#40RichardAtCT merged 3 commits intoRichardAtCT:mainfrom
Conversation
|
PR Review Summary
What looks good
Issues / questions
Suggested tests (if needed)
Verdict
|
|
Addressed the blocker in this PR ✅ What was fixed
Tests added
Docs updated
Both now explicitly state that ValidationRan:
Result: 54 passed, 0 warnings. |
|
PR Review (follow-up) Thanks — I re-checked the incremental changes and confirmed the original blocker is fixed:
One remaining point to consider from the added UTC cleanup commit:
If you add that backward-compat guard, this looks good to merge. |
|
Thanks — I implemented the fix locally, but can’t push from this bot account (403 on both upstream and fork). Posting exact patch as a maintainer-appliable suggestion. Commit (local)
Patchdiff --git a/src/claude/session.py b/src/claude/session.py
index ea3c52c..9ae9468 100644
--- a/src/claude/session.py
+++ b/src/claude/session.py
@@ -27,6 +27,17 @@ ClaudeResponse = Union["CLIClaudeResponse", "SDKClaudeResponse"]
logger = structlog.get_logger()
+def _to_utc(dt: datetime) -> datetime:
+ """Normalize datetime to timezone-aware UTC.
+
+ Backward compatibility: legacy persisted sessions may contain naive
+ timestamps; treat naive values as UTC.
+ """
+ if dt.tzinfo is None:
+ return dt.replace(tzinfo=UTC)
+ return dt.astimezone(UTC)
+
+
@dataclass
class ClaudeSession:
"""Claude Code session state."""
@@ -44,12 +55,12 @@ class ClaudeSession:
def is_expired(self, timeout_hours: int) -> bool:
"""Check if session has expired."""
- age = datetime.now(UTC) - self.last_used
+ age = datetime.now(UTC) - _to_utc(self.last_used)
return age > timedelta(hours=timeout_hours)
def update_usage(self, response: ClaudeResponse) -> None:
"""Update session with usage from response."""
- self.last_used = datetime.now(UTC)
+ self.last_used = _to_utc(datetime.now(UTC))
self.total_cost += response.cost
self.total_turns += response.num_turns
self.message_count += 1
@@ -82,8 +93,8 @@ class ClaudeSession:
session_id=data["session_id"],
user_id=data["user_id"],
project_path=Path(data["project_path"]),
- created_at=datetime.fromisoformat(data["created_at"]),
- last_used=datetime.fromisoformat(data["last_used"]),
+ created_at=_to_utc(datetime.fromisoformat(data["created_at"])),
+ last_used=_to_utc(datetime.fromisoformat(data["last_used"])),
total_cost=data.get("total_cost", 0.0),
total_turns=data.get("total_turns", 0),
message_count=data.get("message_count", 0),
diff --git a/tests/unit/test_claude/test_session.py b/tests/unit/test_claude/test_session.py
index 19ad771..93bc6c4 100644
--- a/tests/unit/test_claude/test_session.py
+++ b/tests/unit/test_claude/test_session.py
@@ -123,6 +123,40 @@ class TestClaudeSession:
assert restored.message_count == original.message_count
assert restored.tools_used == original.tools_used
+ def test_from_dict_normalizes_legacy_naive_timestamps(self):
+ """Legacy naive timestamps should be normalized to UTC-aware datetimes."""
+ data = {
+ "session_id": "test-session",
+ "user_id": 123,
+ "project_path": "/test/path",
+ "created_at": "2026-02-18T10:00:00",
+ "last_used": "2026-02-18T10:30:00",
+ "total_cost": 0.0,
+ "total_turns": 0,
+ "message_count": 0,
+ "tools_used": [],
+ }
+
+ restored = ClaudeSession.from_dict(data)
+
+ assert restored.created_at.tzinfo is not None
+ assert restored.last_used.tzinfo is not None
+ assert restored.created_at.tzinfo == UTC
+ assert restored.last_used.tzinfo == UTC
+
+ def test_is_expired_handles_legacy_naive_last_used(self):
+ """Expiry check should not crash on naive legacy timestamps."""
+ naive_old = datetime.now() - timedelta(hours=30)
+ session = ClaudeSession(
+ session_id="legacy-session",
+ user_id=123,
+ project_path=Path("/test/path"),
+ created_at=naive_old,
+ last_used=naive_old,
+ )
+
+ assert session.is_expired(24) is True
+
class TestInMemorySessionStorage:
"""Test in-memory session storage."""Validation run
|
|
Implemented the patch from the latest review comment and pushed it. Cross-reference for traceability:
Applied on this branch:
Validation:
|
Summary
This PR extracts only the security configuration parts requested in PR #27, leaving output-formatting changes out.
Included
DISABLE_SECURITY_PATTERNSconfig (Settings.disable_security_patterns, defaultfalse)DISABLE_TOOL_VALIDATIONconfig (Settings.disable_tool_validation, defaultfalse)SecurityValidator(..., disable_security_patterns=config.disable_security_patterns)SecurityValidatorupdate:disable_security_patternsconstructor argvalidate_path()when enabledToolMonitorupdate:disable_tool_validation=true.env.examplenew env vars + warningsdocs/configuration.mdnew "Security Relaxation" sectionExplicitly not included
/newsession reset behavior changesclaude/integration.pyWhy
Requested split from original #27 thread so security controls can be reviewed/merged independently from formatting work.
Validation
poetryandpytestunavailable).python3 -m compileall src tests