From ef922b8469bcc338ec5cb3d4e0a946f6556249b0 Mon Sep 17 00:00:00 2001 From: Corey Leath Date: Sat, 18 Jul 2026 15:28:19 -0400 Subject: [PATCH 01/34] Harden backend/services/chat_service.py --- backend/services/chat_service.py | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/backend/services/chat_service.py b/backend/services/chat_service.py index da7042c..f9be32d 100644 --- a/backend/services/chat_service.py +++ b/backend/services/chat_service.py @@ -1,7 +1,9 @@ -import uuid import datetime +import os +import threading +import uuid +from collections import deque from datetime import timezone -from backend.config import settings class ChatService: @@ -11,10 +13,16 @@ class ChatService: to be upgraded easily to Firebase, Redis, or SQL. """ - def __init__(self): - # Temporary in-memory message store - # Structure: { "id": str, "username": str, "content": str, "timestamp": str } - self.messages = [] + def __init__(self, max_messages: int | None = None): + """Create a bounded, thread-safe store for one process. + + A durable shared store is still required for multi-instance production deployments. + """ + configured_limit = max_messages or int(os.getenv("CHAT_HISTORY_LIMIT", "10000")) + if configured_limit < 1: + raise ValueError("max_messages must be positive") + self.messages = deque(maxlen=configured_limit) + self._lock = threading.RLock() def _generate_message_id(self): """ @@ -44,7 +52,8 @@ def send_message(self, username: str, content: str): } # In-memory storage (can replace with Firebase/Postgres/etc.) - self.messages.append(message) + with self._lock: + self.messages.append(message) return message @@ -52,4 +61,7 @@ def get_messages(self, limit: int = 50): """ Returns the latest 'limit' chat messages. """ - return self.messages[-limit:] + if limit < 1: + return [] + with self._lock: + return list(self.messages)[-limit:] From 25445f392e2218f3e7d3e9c63218737537db513c Mon Sep 17 00:00:00 2001 From: Corey Leath Date: Sat, 18 Jul 2026 15:28:25 -0400 Subject: [PATCH 02/34] Harden backend/routes/chat_routes.py --- backend/routes/chat_routes.py | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/backend/routes/chat_routes.py b/backend/routes/chat_routes.py index 0488230..51aea72 100644 --- a/backend/routes/chat_routes.py +++ b/backend/routes/chat_routes.py @@ -1,5 +1,6 @@ from fastapi import APIRouter, HTTPException -from pydantic import BaseModel +from pydantic import BaseModel, ConfigDict, Field + from backend.services.chat_service import ChatService router = APIRouter() @@ -11,8 +12,9 @@ # ------------------------------------------- class MessageRequest(BaseModel): - username: str - content: str + model_config = ConfigDict(extra="forbid", str_strip_whitespace=True) + username: str = Field(min_length=1, max_length=32, pattern=r"^[\w .'-]+$") + content: str = Field(min_length=1, max_length=2_000) class MessageResponse(BaseModel): @@ -37,8 +39,8 @@ async def send_message(message: MessageRequest): content=message.content ) return new_message - except Exception as e: - raise HTTPException(status_code=500, detail=str(e)) + except Exception as exc: + raise HTTPException(status_code=503, detail="Message store unavailable") from exc @router.get("/history") @@ -47,7 +49,11 @@ async def get_message_history(limit: int = 50): Returns the latest N chat messages. """ try: + if not 1 <= limit <= 200: + raise HTTPException(status_code=422, detail="limit must be between 1 and 200") messages = chat_service.get_messages(limit=limit) return messages - except Exception as e: - raise HTTPException(status_code=500, detail=str(e)) + except HTTPException: + raise + except Exception as exc: + raise HTTPException(status_code=503, detail="Message store unavailable") from exc From ae62bf55eb862aca7d40516dedfb088b7c462bf7 Mon Sep 17 00:00:00 2001 From: Corey Leath Date: Sat, 18 Jul 2026 15:28:29 -0400 Subject: [PATCH 03/34] Harden backend/api.py --- backend/api.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/backend/api.py b/backend/api.py index cb83697..fa6180c 100644 --- a/backend/api.py +++ b/backend/api.py @@ -1,6 +1,9 @@ from fastapi import FastAPI + from backend.config import settings from backend.routes.chat_routes import router as chat_router +from backend.routes.ws_routes import router as websocket_router + def create_app() -> FastAPI: """ @@ -15,6 +18,7 @@ def create_app() -> FastAPI: # Include application routes app.include_router(chat_router, prefix="/chat", tags=["Chat"]) + app.include_router(websocket_router, tags=["WebSocket"]) @app.get("/", tags=["Health"]) async def root(): From cb3bb348015ed75328f68d9cde0a421ff58fcc24 Mon Sep 17 00:00:00 2001 From: Corey Leath Date: Sat, 18 Jul 2026 15:28:35 -0400 Subject: [PATCH 04/34] Harden backend/config.py --- backend/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/config.py b/backend/config.py index 7668d4c..699df4c 100644 --- a/backend/config.py +++ b/backend/config.py @@ -18,7 +18,7 @@ class Settings: API_VERSION: str = "v1" # Local development settings - HOST: str = os.getenv("HOST", "0.0.0.0") + HOST: str = os.getenv("HOST", "127.0.0.1") PORT: int = int(os.getenv("PORT", "10000")) settings = Settings() From aaab23497d440c8bd6eeab51734fb885dd62a07d Mon Sep 17 00:00:00 2001 From: Corey Leath Date: Sat, 18 Jul 2026 15:28:40 -0400 Subject: [PATCH 05/34] Harden app/core/security.py --- app/core/security.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/core/security.py b/app/core/security.py index 36da76e..ea60b9f 100644 --- a/app/core/security.py +++ b/app/core/security.py @@ -2,9 +2,9 @@ import os from datetime import datetime, timedelta, timezone -from typing import Optional -from jose import JWTError, jwt +import jwt +from jwt import InvalidTokenError from passlib.context import CryptContext SECRET_KEY = os.getenv("SECRET_KEY") @@ -27,7 +27,7 @@ def verify_password(plain_password: str, hashed_password: str) -> bool: return pwd_context.verify(plain_password, hashed_password) -def create_access_token(data: dict, expires_delta: Optional[timedelta] = None): +def create_access_token(data: dict, expires_delta: timedelta | None = None): to_encode = data.copy() expire = datetime.now(timezone.utc) + ( @@ -42,5 +42,5 @@ def verify_token(token: str): try: payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM]) return payload - except JWTError: - return None \ No newline at end of file + except InvalidTokenError: + return None From 74c635a86c23ea60eef3ae2a918c2fd023948b07 Mon Sep 17 00:00:00 2001 From: Corey Leath Date: Sat, 18 Jul 2026 15:28:45 -0400 Subject: [PATCH 06/34] Harden requirements.txt --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index c76e559..8faf598 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,6 +11,6 @@ uvicorn>=0.34.0 httpx>=0.28.1 redis>=5.2.0 prometheus-client>=0.21.0 -python-jose[cryptography]>=3.3.0 +PyJWT>=2.12.0 passlib[bcrypt]>=1.7.4 python-json-logger>=2.0.7 From 7d12fbed0b87997d5baf7b4af49795913cd31843 Mon Sep 17 00:00:00 2001 From: Corey Leath Date: Sat, 18 Jul 2026 15:28:47 -0400 Subject: [PATCH 07/34] Harden .github/workflows/trojanchat-hygiene.yml --- .github/workflows/trojanchat-hygiene.yml | 31 +++++++++++++++++------- 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/.github/workflows/trojanchat-hygiene.yml b/.github/workflows/trojanchat-hygiene.yml index 4c65498..576df5d 100644 --- a/.github/workflows/trojanchat-hygiene.yml +++ b/.github/workflows/trojanchat-hygiene.yml @@ -26,11 +26,19 @@ jobs: python-version: "3.11" cache: pip - name: Install quality tooling - run: python -m pip install --upgrade pip ruff black + run: python -m pip install --upgrade pip ruff black bandit - name: Validate Python syntax run: python -m compileall -q app backend tests - - name: Run blocking Ruff correctness checks - run: ruff check app backend tests/test_api.py tests/test_chat_service.py tests/test_inference_cache.py --select E9,F63,F7,F82 + - name: Run blocking Ruff and Bandit checks + run: | + ruff check backend/api.py backend/routes backend/services app/core/security.py benchmarks tests/test_api.py tests/test_backend_hardening.py tests/test_benchmark.py tests/test_security.py + bandit -q backend/api.py backend/config.py backend/routes/*.py backend/services/*.py app/core/inference_cache.py app/core/security.py app/services/chat_service.py -f json -o bandit-report.json + - name: Upload static-analysis report + if: always() + uses: actions/upload-artifact@v4 + with: + name: static-analysis-report + path: bandit-report.json - name: Publish formatting advisory run: | black --check app backend tests/test_api.py tests/test_chat_service.py tests/test_inference_cache.py \ @@ -63,7 +71,16 @@ jobs: tests/test_api.py \ tests/test_chat_service.py \ tests/test_inference_cache.py \ - --cov=app \ + tests/test_backend_hardening.py \ + tests/test_benchmark.py \ + tests/test_security.py \ + --cov=backend.api \ + --cov=backend.routes.chat_routes \ + --cov=backend.routes.ws_routes \ + --cov=backend.services.chat_service \ + --cov=app.core.inference_cache \ + --cov=app.services.chat_service \ + --cov-fail-under=90 \ --cov-report=term-missing \ --cov-report=xml \ --junitxml=pytest-results.xml \ @@ -90,13 +107,9 @@ jobs: cache: pip - name: Install audit tooling run: pip install pip-audit - - name: Generate dependency audit report + - name: Enforce dependency audit run: | - set +e pip-audit -r requirements.txt --progress-spinner off --format json --output pip-audit.json - status=$? - echo "pip-audit completed with status ${status}; findings are retained as an artifact for triage." - exit 0 - name: Upload dependency audit uses: actions/upload-artifact@v4 with: From 5b28865cb8863bcf96a82e4556305f155b564f79 Mon Sep 17 00:00:00 2001 From: Corey Leath Date: Sat, 18 Jul 2026 15:28:51 -0400 Subject: [PATCH 08/34] Harden .github/workflows/benchmarks.yml --- .github/workflows/benchmarks.yml | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/.github/workflows/benchmarks.yml b/.github/workflows/benchmarks.yml index 4c662c0..8f51a6b 100644 --- a/.github/workflows/benchmarks.yml +++ b/.github/workflows/benchmarks.yml @@ -19,20 +19,26 @@ jobs: with: python-version: '3.11' - - name: Install Performance Harness Dependencies - run: | - if [ -f "requirements.txt" ]; then pip install -r requirements.txt; fi - pip install pytest pytest-benchmark pytest-asyncio websockets cryptography fastapi + - name: Install benchmark dependencies + run: pip install -r requirements-dev.txt - name: Execute Profiling Suite & Track Latency Metrics env: PYTHONPATH: . run: | - echo "Executing high-concurrency message broadcast and cryptographic routine benchmarks..." - # Runs benchmarks if present, otherwise outputs structured system performance logging - if [ -d "tests/benchmarks" ]; then - pytest tests/benchmarks/ --benchmark-json=output.json - else - echo "Executing isolated performance profile for local encryption times and pub/sub message dispatch speeds..." - echo "Performance metrics captured successfully. Broadcast pipelines remain sub-millisecond." - fi + python -m benchmarks.run_benchmark --output benchmarks/latest.json + python -m pytest tests/test_benchmark.py -q + python - <<'PY' + import json + result = json.load(open("benchmarks/latest.json", encoding="utf-8")) + regression = result["comparison"]["throughput_change_percent"] + if regression < -15: + raise SystemExit(f"Throughput regression {regression}% exceeds 15% budget") + PY + - name: Upload benchmark evidence + uses: actions/upload-artifact@v4 + with: + name: benchmark-report + path: | + benchmarks/latest.json + benchmarks/benchmark_report.md From 4c09efeeefc11616b0f1e976586ae9f2799d6418 Mon Sep 17 00:00:00 2001 From: Corey Leath Date: Sat, 18 Jul 2026 15:28:57 -0400 Subject: [PATCH 09/34] Harden .github/workflows/security-supply-chain.yml --- .github/workflows/security-supply-chain.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/security-supply-chain.yml b/.github/workflows/security-supply-chain.yml index 08c337a..d5cb24f 100644 --- a/.github/workflows/security-supply-chain.yml +++ b/.github/workflows/security-supply-chain.yml @@ -55,7 +55,7 @@ jobs: output: trivy-results.sarif severity: HIGH,CRITICAL ignore-unfixed: true - exit-code: "0" + exit-code: "1" - name: Upload SARIF uses: github/codeql-action/upload-sarif@v4 with: @@ -88,4 +88,4 @@ jobs: format: table severity: HIGH,CRITICAL ignore-unfixed: true - exit-code: "0" + exit-code: "1" From 89265c70f75f4ae0e35ad15bff5ef707bfd95531 Mon Sep 17 00:00:00 2001 From: Corey Leath Date: Sat, 18 Jul 2026 15:29:01 -0400 Subject: [PATCH 10/34] Add .github/workflows/codeql.yml --- .github/workflows/codeql.yml | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 .github/workflows/codeql.yml diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 0000000..2af2be4 --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,26 @@ +name: CodeQL + +on: + pull_request: + branches: [main] + push: + branches: [main] + schedule: + - cron: "31 5 * * 1" + +permissions: + contents: read + security-events: write + +jobs: + analyze: + runs-on: ubuntu-latest + strategy: + matrix: + language: [python, javascript-typescript] + steps: + - uses: actions/checkout@v4 + - uses: github/codeql-action/init@v4 + with: + languages: ${{ matrix.language }} + - uses: github/codeql-action/analyze@v4 From 12072d3d64018d9ecc6d8d3318c650602f82cc40 Mon Sep 17 00:00:00 2001 From: Corey Leath Date: Sat, 18 Jul 2026 15:29:05 -0400 Subject: [PATCH 11/34] Add tests/test_backend_hardening.py --- tests/test_backend_hardening.py | 75 +++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 tests/test_backend_hardening.py diff --git a/tests/test_backend_hardening.py b/tests/test_backend_hardening.py new file mode 100644 index 0000000..c6d997c --- /dev/null +++ b/tests/test_backend_hardening.py @@ -0,0 +1,75 @@ +"""Unit and integration coverage for bounded storage and API trust boundaries.""" + +import asyncio +from unittest.mock import AsyncMock + +import pytest +from backend.api import app +from backend.routes.ws_routes import ConnectionManager +from backend.services.chat_service import ChatService +from httpx import ASGITransport, AsyncClient + + +def test_store_evicts_oldest_message_and_returns_copy() -> None: + service = ChatService(max_messages=2) + service.send_message("a", "one") + service.send_message("b", "two") + service.send_message("c", "three") + history = service.get_messages(50) + assert [message["content"] for message in history] == ["two", "three"] + history.clear() + assert len(service.get_messages(50)) == 2 + + +def test_store_rejects_invalid_capacity_and_nonpositive_limit() -> None: + with pytest.raises(ValueError, match="positive"): + ChatService(max_messages=-1) + assert ChatService(1).get_messages(0) == [] + + +@pytest.mark.asyncio +@pytest.mark.parametrize( + "payload", + [ + {"username": "", "content": "hello"}, + {"username": "