security: fix env race, signing key, upload traversal, password hash (#106)
* security: fix four audit findings -- env race, signing key, upload traversal, password hash 1. Race condition in os.environ (HIGH): Per-session _agent_lock didn't prevent cross-session env writes from interleaving. Added global _ENV_LOCK in streaming.py that serializes the entire env save/restore block across all sessions. 2. Predictable signing key (MEDIUM): sha256(STATE_DIR) was deterministic. Now generates a random 32-byte key on first startup and persists it to STATE_DIR/.signing_key (chmod 600). Existing sessions invalidated on first restart (acceptable for a security fix). 3. Upload path traversal (MEDIUM): Filename '..' survived the regex sanitization (dots are allowed chars). Added explicit rejection of dot-only names and safe_resolve_ws() check to verify the resolved path stays within the workspace. 4. Weak password hashing (MEDIUM): Replaced bare SHA-256 with PBKDF2- SHA256 (600k iterations per OWASP). Uses stdlib hashlib.pbkdf2_hmac, no new dependencies. Note: existing passwords must be re-set after this change (hash format changed). Closes #106 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: use random signing key as PBKDF2 salt (replaces predictable STATE_DIR salt) --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -17,6 +17,12 @@ from api.config import (
|
||||
resolve_model_provider,
|
||||
)
|
||||
|
||||
# Global lock for os.environ writes. Per-session locks (_agent_lock) prevent
|
||||
# concurrent runs of the SAME session, but two DIFFERENT sessions can still
|
||||
# interleave their os.environ writes. This global lock serializes the env
|
||||
# save/restore around the entire agent run.
|
||||
_ENV_LOCK = threading.Lock()
|
||||
|
||||
# Lazy import to avoid circular deps -- hermes-agent is on sys.path via api/config.py
|
||||
try:
|
||||
from run_agent import AIAgent
|
||||
@@ -101,7 +107,7 @@ def _run_agent_streaming(session_id, msg_text, model, workspace, stream_id, atta
|
||||
HERMES_HOME=_profile_home,
|
||||
)
|
||||
# Still set process-level env as fallback for tools that bypass thread-local
|
||||
with _agent_lock:
|
||||
with _ENV_LOCK:
|
||||
old_cwd = os.environ.get('TERMINAL_CWD')
|
||||
old_exec_ask = os.environ.get('HERMES_EXEC_ASK')
|
||||
old_session_key = os.environ.get('HERMES_SESSION_KEY')
|
||||
|
||||
Reference in New Issue
Block a user