This repository has been archived on 2026-03-24. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
Timmy-time-dashboard/tests/dashboard/middleware/test_csrf_bypass_vulnerability.py
Alexander Whitestone 36fc10097f Claude/angry cerf (#173)
* feat: set qwen3.5:latest as default model

- Make qwen3.5:latest the primary default model for faster inference
- Move llama3.1:8b-instruct to fallback chain
- Update text fallback chain to prioritize qwen3.5:latest

Retains full backward compatibility via cascade fallback.

* test: remove ~55 brittle, duplicate, and useless tests

Audit of all 100 test files identified tests that provided no real
regression protection. Removed:

- 4 files deleted entirely: test_setup_script (always skipped),
  test_csrf_bypass (tautological assertions), test_input_validation
  (accepts 200-500 status codes), test_security_regression (fragile
  source-pattern checks redundant with rendering tests)
- Duplicate test classes (TestToolTracking, TestCalculatorExtended)
- Mock-only tests that just verify mock wiring, not behavior
- Structurally broken tests (TestCreateToolFunctions patches after import)
- Empty/pass-body tests and meaningless assertions (len > 20)
- Flaky subprocess tests (aider tool calling real binary)

All 1328 remaining tests pass. Net: -699 lines, zero coverage loss.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: prevent test pollution from autoresearch_enabled mutation

test_autoresearch_perplexity.py was setting settings.autoresearch_enabled = True
but never restoring it in the finally block — polluting subsequent tests.
When pytest-randomly ordered it before test_experiments_page_shows_disabled_when_off,
the victim test saw enabled=True and failed to find "Disabled" in the page.

Fix both sides:
- Restore autoresearch_enabled in the finally block (root cause)
- Mock settings explicitly in the victim test (defense in depth)

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>
2026-03-11 16:55:27 -04:00

108 lines
3.6 KiB
Python

"""Tests for CSRF protection middleware bypass vulnerabilities."""
import pytest
from fastapi import FastAPI
from fastapi.testclient import TestClient
from dashboard.middleware.csrf import CSRFMiddleware
class TestCSRFBypassVulnerability:
"""Test CSRF bypass via path normalization and suffix matching."""
@pytest.fixture(autouse=True)
def enable_csrf(self):
"""Re-enable CSRF for these tests."""
from config import settings
original = settings.timmy_disable_csrf
settings.timmy_disable_csrf = False
yield
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.
The middleware uses os.path.normpath() on the request path and then checks
if it starts with an exempt pattern. If the request is to '/webhook/../api/chat',
normpath makes it '/api/chat', which DOES NOT start with '/webhook'.
Wait, the vulnerability is actually the OTHER way around:
If I want to access '/api/chat' (protected) but I use '/webhook/../api/chat',
normpath makes it '/api/chat', which is NOT exempt.
HOWEVER, if the middleware DOES NOT use normpath, then '/webhook/../api/chat'
WOULD start with '/webhook' and be exempt.
The current code DOES use normpath:
```python
normalized_path = os.path.normpath(path)
if not normalized_path.startswith("/"):
normalized_path = "/" + normalized_path
```
Let's look at the exempt patterns again:
exempt_patterns = [
"/webhook",
"/api/v1/",
"/lightning/webhook",
"/_internal/",
]
If I have a route '/webhook_attacker' that is NOT exempt,
but it starts with '/webhook', it WILL be exempt.
"""
app = FastAPI()
app.add_middleware(CSRFMiddleware)
@app.post("/webhook_attacker")
def sensitive_endpoint():
return {"message": "sensitive data accessed"}
client = TestClient(app)
# This route should NOT be exempt, but it starts with '/webhook'
# CSRF validation should fail (403) because we provide no token.
response = client.post("/webhook_attacker")
# If it's 200, it's a bypass!
assert response.status_code == 403, "Route /webhook_attacker should be protected by CSRF"
def test_csrf_bypass_via_webhook_prefix(self):
"""Test if /webhook_secret is exempt because it starts with /webhook."""
app = FastAPI()
app.add_middleware(CSRFMiddleware)
@app.post("/webhook_secret")
def sensitive_endpoint():
return {"message": "sensitive data accessed"}
client = TestClient(app)
# Should be 403
response = client.post("/webhook_secret")
assert response.status_code == 403, "Route /webhook_secret should be protected by CSRF"
def test_legitimate_exempt_paths(self):
"""Test that legitimate exempt paths still work."""
app = FastAPI()
app.add_middleware(CSRFMiddleware)
@app.post("/webhook")
def webhook():
return {"message": "webhook received"}
@app.post("/api/v1/chat")
def api_chat():
return {"message": "api chat"}
client = TestClient(app)
# Legitimate /webhook (exact match)
response = client.post("/webhook")
assert response.status_code == 200
# Legitimate /api/v1/chat (prefix match)
response = client.post("/api/v1/chat")
assert response.status_code == 200