security: bandit fixes B310/B324/B110 + QuietHTTPServer (#354)
* security: fix bandit security issues (B310, B324) - Add usedforsecurity=False to MD5 hash in gateway_watcher.py - Add URL scheme validation to prevent file:// access in config.py - Add URL validation to bootstrap.py health check - Add nosec comments where runtime validation exists * fix: handle ConnectionResetError gracefully and add debug logging - Add QuietHTTPServer class to suppress noisy connection reset errors caused by clients disconnecting abruptly (fixes log spam from 'ConnectionResetError: [Errno 54] Connection reset by peer') - Replace silent 'pass' statements with logger.debug() calls across api/auth.py, api/config.py, api/gateway_watcher.py, api/models.py, and api/onboarding.py for better observability during troubleshooting - All tests pass (25 passed in test_regressions.py) * chore: add debug logging to profiles and routes modules - Replace silent 'pass' statements with logger.debug() calls in api/profiles.py for better error visibility during profile switching and module patching - Add logger initialization to api/routes.py * security: fix B110 bare except/pass issues (bandit security scan) - Replace bare except/pass patterns with logger.debug() calls - Fixes CWE-703 (improper check/handling of exceptional conditions) - Files affected: routes.py, state_sync.py, streaming.py, workspace.py, server.py - All tests pass successfully * security: bandit fixes B310/B324/B110 + QuietHTTPServer (#354) - api/gateway_watcher.py: MD5 usedforsecurity=False (B324) - api/config.py, bootstrap.py: URL scheme validation before urlopen (B310) - 12 files: replace bare except/pass with logger.debug() (B110) - server.py: QuietHTTPServer suppresses client disconnect log noise - server.py: fix sys.exc_info() (was traceback.sys.exc_info(), impl detail) - tests/test_sprint43.py: 19 new tests covering all security fixes - CHANGELOG.md: v0.50.14 entry; 841 tests total (up from 822) --------- Co-authored-by: lawrencel1ng <lawrence.ling@global.ntt> Co-authored-by: Nathan Esquenazi <nesquena@gmail.com>
This commit is contained in:
@@ -5,6 +5,7 @@ Extracted from server.py (Sprint 11) so server.py is a thin shell.
|
||||
|
||||
import html as _html
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import queue
|
||||
import sys
|
||||
@@ -14,6 +15,8 @@ import uuid
|
||||
from pathlib import Path
|
||||
from urllib.parse import parse_qs
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
from api.config import (
|
||||
STATE_DIR,
|
||||
SESSION_DIR,
|
||||
@@ -330,6 +333,10 @@ def handle_get(handler, parsed) -> bool:
|
||||
deduped_cli = []
|
||||
merged = webui_sessions + deduped_cli
|
||||
merged.sort(key=lambda s: s.get("updated_at", 0) or 0, reverse=True)
|
||||
# Redact credentials from session titles before returning
|
||||
for s in merged:
|
||||
if isinstance(s.get("title"), str):
|
||||
s["title"] = _redact_text(s["title"])
|
||||
return j(handler, {"sessions": merged, "cli_count": len(deduped_cli)})
|
||||
|
||||
if parsed.path == "/api/projects":
|
||||
@@ -642,18 +649,18 @@ def handle_post(handler, parsed) -> bool:
|
||||
try:
|
||||
p.unlink(missing_ok=True)
|
||||
except Exception:
|
||||
pass
|
||||
logger.debug("Failed to unlink session file %s", p)
|
||||
try:
|
||||
SESSION_INDEX_FILE.unlink(missing_ok=True)
|
||||
except Exception:
|
||||
pass
|
||||
logger.debug("Failed to unlink session index")
|
||||
# Also delete from CLI state.db (for CLI sessions shown in sidebar)
|
||||
try:
|
||||
from api.models import delete_cli_session
|
||||
|
||||
delete_cli_session(sid)
|
||||
except Exception:
|
||||
pass
|
||||
logger.debug("Failed to delete CLI session %s", sid)
|
||||
return j(handler, {"ok": True})
|
||||
|
||||
if parsed.path == "/api/session/clear":
|
||||
@@ -963,9 +970,9 @@ def handle_post(handler, parsed) -> bool:
|
||||
s.project_id = None
|
||||
s.save()
|
||||
except Exception:
|
||||
pass
|
||||
logger.debug("Failed to update session %s", entry.get("session_id"))
|
||||
except Exception:
|
||||
pass
|
||||
logger.debug("Failed to load session index for project unlink")
|
||||
return j(handler, {"ok": True})
|
||||
|
||||
# ── Session import from JSON (POST) ──
|
||||
@@ -1330,7 +1337,7 @@ def _handle_cron_output(handler, parsed):
|
||||
txt = f.read_text(encoding="utf-8", errors="replace")
|
||||
outputs.append({"filename": f.name, "content": txt[:8000]})
|
||||
except Exception:
|
||||
pass
|
||||
logger.debug("Failed to read cron output file %s", f)
|
||||
return j(handler, {"job_id": job_id, "outputs": outputs})
|
||||
|
||||
|
||||
@@ -1424,7 +1431,7 @@ def _handle_sessions_cleanup(handler, body, zero_only=False):
|
||||
p.unlink(missing_ok=True)
|
||||
cleaned += 1
|
||||
except Exception:
|
||||
pass
|
||||
logger.debug("Failed to clean up session file %s", p)
|
||||
if SESSION_INDEX_FILE.exists():
|
||||
SESSION_INDEX_FILE.unlink(missing_ok=True)
|
||||
return j(handler, {"ok": True, "cleaned": cleaned})
|
||||
@@ -1571,7 +1578,7 @@ def _handle_chat_sync(handler, body):
|
||||
message_count=len(s.messages),
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
logger.debug("Failed to update session cost tracking")
|
||||
return j(
|
||||
handler,
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user