refactor: centralize config & harden security (#141)
* feat: upgrade primary model from llama3.1:8b to qwen2.5:14b - Swap OLLAMA_MODEL_PRIMARY to qwen2.5:14b for better reasoning - llama3.1:8b-instruct becomes fallback - Update .env default and README quick start - Fix hardcoded model assertions in tests qwen2.5:14b provides significantly better multi-step reasoning and tool calling reliability while still running locally on modest hardware. The 8B model remains as automatic fallback. * security: centralize config, harden uploads, fix silent exceptions - Add 9 pydantic Settings fields (skip_embeddings, disable_csrf, rqlite_url, brain_source, brain_db_path, csrf_cookie_secure, chat_api_max_body_bytes, timmy_test_mode) to centralize env-var access - Migrate 8 os.environ.get() calls across 5 source files to use `from config import settings` per project convention - Add path traversal defense-in-depth to file upload endpoint - Add 1MB request body size limit to chat API - Make CSRF cookie secure flag configurable via settings - Replace 2 silent `except: pass` blocks with debug logging in session.py - Remove unused `import os` from brain/memory.py and csrf.py - Update 5 CSRF test fixtures to patch settings instead of os.environ Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Trip T <trip@local> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
committed by
GitHub
parent
cdd3e1a90b
commit
b615595100
@@ -12,14 +12,11 @@ class TestCSRFMiddleware:
|
||||
@pytest.fixture(autouse=True)
|
||||
def enable_csrf(self):
|
||||
"""Re-enable CSRF for these tests."""
|
||||
import os
|
||||
old_val = os.environ.get("TIMMY_DISABLE_CSRF")
|
||||
os.environ["TIMMY_DISABLE_CSRF"] = "0"
|
||||
from config import settings
|
||||
original = settings.timmy_disable_csrf
|
||||
settings.timmy_disable_csrf = False
|
||||
yield
|
||||
if old_val is not None:
|
||||
os.environ["TIMMY_DISABLE_CSRF"] = old_val
|
||||
else:
|
||||
del os.environ["TIMMY_DISABLE_CSRF"]
|
||||
settings.timmy_disable_csrf = original
|
||||
|
||||
def test_csrf_token_generation(self):
|
||||
"""CSRF token should be generated and stored in session/state."""
|
||||
|
||||
@@ -11,14 +11,11 @@ class TestCSRFBypass:
|
||||
@pytest.fixture(autouse=True)
|
||||
def enable_csrf(self):
|
||||
"""Re-enable CSRF for these tests."""
|
||||
import os
|
||||
old_val = os.environ.get("TIMMY_DISABLE_CSRF")
|
||||
os.environ["TIMMY_DISABLE_CSRF"] = "0"
|
||||
from config import settings
|
||||
original = settings.timmy_disable_csrf
|
||||
settings.timmy_disable_csrf = False
|
||||
yield
|
||||
if old_val is not None:
|
||||
os.environ["TIMMY_DISABLE_CSRF"] = old_val
|
||||
else:
|
||||
del os.environ["TIMMY_DISABLE_CSRF"]
|
||||
settings.timmy_disable_csrf = original
|
||||
|
||||
def test_csrf_middleware_blocks_unsafe_methods_without_token(self):
|
||||
"""POST should require CSRF token even with AJAX headers (if not explicitly allowed)."""
|
||||
|
||||
@@ -11,14 +11,11 @@ class TestCSRFBypassVulnerability:
|
||||
@pytest.fixture(autouse=True)
|
||||
def enable_csrf(self):
|
||||
"""Re-enable CSRF for these tests."""
|
||||
import os
|
||||
old_val = os.environ.get("TIMMY_DISABLE_CSRF")
|
||||
os.environ["TIMMY_DISABLE_CSRF"] = "0"
|
||||
from config import settings
|
||||
original = settings.timmy_disable_csrf
|
||||
settings.timmy_disable_csrf = False
|
||||
yield
|
||||
if old_val is not None:
|
||||
os.environ["TIMMY_DISABLE_CSRF"] = old_val
|
||||
else:
|
||||
del os.environ["TIMMY_DISABLE_CSRF"]
|
||||
settings.timmy_disable_csrf = original
|
||||
|
||||
def test_csrf_bypass_via_traversal_to_exempt_pattern(self):
|
||||
"""Test if a non-exempt route can be accessed by traversing to an exempt pattern.
|
||||
|
||||
@@ -11,14 +11,11 @@ class TestCSRFTraversal:
|
||||
@pytest.fixture(autouse=True)
|
||||
def enable_csrf(self):
|
||||
"""Re-enable CSRF for these tests."""
|
||||
import os
|
||||
old_val = os.environ.get("TIMMY_DISABLE_CSRF")
|
||||
os.environ["TIMMY_DISABLE_CSRF"] = "0"
|
||||
from config import settings
|
||||
original = settings.timmy_disable_csrf
|
||||
settings.timmy_disable_csrf = False
|
||||
yield
|
||||
if old_val is not None:
|
||||
os.environ["TIMMY_DISABLE_CSRF"] = old_val
|
||||
else:
|
||||
del os.environ["TIMMY_DISABLE_CSRF"]
|
||||
settings.timmy_disable_csrf = original
|
||||
|
||||
def test_csrf_middleware_path_traversal_bypass(self):
|
||||
"""Test if path traversal can bypass CSRF exempt patterns."""
|
||||
|
||||
@@ -22,21 +22,18 @@ def test_request_logging_middleware_is_used(client):
|
||||
|
||||
def test_csrf_middleware_is_used(client):
|
||||
"""Verify that CSRFMiddleware is used."""
|
||||
import os
|
||||
old_val = os.environ.get("TIMMY_DISABLE_CSRF")
|
||||
os.environ["TIMMY_DISABLE_CSRF"] = "0"
|
||||
from config import settings
|
||||
original = settings.timmy_disable_csrf
|
||||
settings.timmy_disable_csrf = False
|
||||
try:
|
||||
# GET request should set a csrf_token cookie if not present
|
||||
response = client.get("/")
|
||||
assert "csrf_token" in response.cookies
|
||||
|
||||
|
||||
# POST request without token should be blocked (403)
|
||||
# Use a path that isn't likely to be exempt
|
||||
response = client.post("/agents/create")
|
||||
assert response.status_code == 403
|
||||
assert response.json()["code"] == "CSRF_INVALID"
|
||||
finally:
|
||||
if old_val is not None:
|
||||
os.environ["TIMMY_DISABLE_CSRF"] = old_val
|
||||
else:
|
||||
del os.environ["TIMMY_DISABLE_CSRF"]
|
||||
settings.timmy_disable_csrf = original
|
||||
|
||||
Reference in New Issue
Block a user