Skip to content

Research-grade evidence, bounded chat storage, and blocking quality gates#27

Merged
CoreyLeath-code merged 34 commits into
mainfrom
agent/research-evidence-hardening
Jul 18, 2026
Merged

Research-grade evidence, bounded chat storage, and blocking quality gates#27
CoreyLeath-code merged 34 commits into
mainfrom
agent/research-evidence-hardening

Conversation

@CoreyLeath-code

@CoreyLeath-code CoreyLeath-code commented Jul 18, 2026

Copy link
Copy Markdown
Owner

Executive summary

This PR converts TrojanChat's quality and performance claims into reproducible, blocking engineering evidence while hardening the canonical backend.api:app path.

Product and architecture changes

  • bound in-process history with configurable CHAT_HISTORY_LIMIT and thread-safe retention
  • mount the existing WebSocket router in the canonical FastAPI app
  • enforce strict username/content/history limits and reject extra request fields
  • sanitize internal storage failures instead of reflecting exception text
  • change the local bind default to loopback
  • replace python-jose/vulnerable transitive ecdsa with PyJWT for the existing HS256 flow

Tests and coverage

  • add unit tests for retention/eviction and JWT regression behavior
  • add negative and edge-case API integration tests
  • add WebSocket connection/broadcast failure lifecycle tests
  • enforce 90% critical-path coverage in Python 3.11 and 3.12 CI
  • local result: 32 passed, 92.57% coverage

Research benchmark

A versioned harness compares identical 50,000-message workloads over seven iterations and records mean, median, P95, P99, min/max latency, throughput, peak Python memory, parameters, and environment.

Metric Legacy Bounded store Change
Median latency 1,155.519 ms 1,239.880 ms +7.3%
Throughput 43,270.60 msg/s 40,326.50 msg/s -6.8%
Peak Python memory 21.205 MiB 4.228 MiB -80.06%

The throughput tradeoff stays inside the documented 15% regression budget. Raw JSON and methodology are committed; CI regenerates the artifact.

CI, static analysis, and security

  • replace placeholder benchmark success with an executable regression gate
  • make pip-audit and actionable HIGH/CRITICAL Trivy findings blocking
  • add Ruff + Bandit enforcement and retain Bandit JSON
  • add Python and JavaScript/TypeScript CodeQL analysis
  • retain Gitleaks, CycloneDX SBOM, Dependabot, container build, non-root and health smoke tests

Documentation and release readiness

  • add production audit with prioritized findings and residual risks
  • add production checklist and research benchmark report
  • add evidence-backed README dashboard and live workflow/release badges
  • update the Unreleased changelog for the next semantic release

Local verification

  • Ruff: passed
  • Bandit: passed with zero findings
  • pytest: 32 passed
  • coverage: 92.57% (90% gate)
  • pip-audit: no known vulnerabilities
  • workflow YAML: parsed successfully
  • Docker: deferred to GitHub Actions because the local daemon was unavailable

Rollout considerations

The store remains process-local and intentionally bounded; multi-instance production requires a durable shared store. The WebSocket endpoint still requires authentication, origin enforcement, rate limiting, and TLS termination before public exposure. These residual risks are explicit in docs/AUDIT.md and docs/PRODUCTION_CHECKLIST.md.

Summary by CodeRabbit

  • New Features

    • Added real-time WebSocket chat connectivity.
    • Chat history now has configurable limits and safely removes older messages when full.
    • Added reproducible performance benchmarks and published results.
  • Bug Fixes

    • Improved handling of invalid authentication tokens and unavailable message storage.
    • Added stricter validation for usernames, message content, and history limits.
  • Security

    • Security and dependency scans now block releases when issues are detected.
    • Added expanded code analysis and coverage enforcement.
  • Documentation

    • Updated the changelog, README, production checklist, and audit documentation.

@coderabbitai

coderabbitai Bot commented Jul 18, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@CoreyLeath-code, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 35 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 9f7ee2f3-a379-4a48-98b0-92fa955801d2

📥 Commits

Reviewing files that changed from the base of the PR and between 47b02bb and fdd7a40.

⛔ Files ignored due to path filters (1)
  • frontend/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (7)
  • .github/workflows/security-supply-chain.yml
  • .github/workflows/trojanchat-hygiene.yml
  • CHANGELOG.MD
  • Dockerfile
  • README.md
  • docs/AUDIT.md
  • frontend/package.json
📝 Walkthrough

Walkthrough

The PR hardens chat validation and storage, switches JWT handling to PyJWT, mounts WebSockets, adds reproducible benchmark evidence, strengthens CI security and coverage gates, and documents production-readiness findings and checks.

Changes

TrojanChat hardening

Layer / File(s) Summary
Runtime security and chat boundaries
requirements.txt, app/core/security.py, backend/api.py, backend/config.py, backend/routes/chat_routes.py, backend/services/chat_service.py, tests/test_backend_hardening.py, tests/test_security.py
JWT verification uses PyJWT, WebSocket routes are registered, chat inputs and history limits are constrained, failures return sanitized 503 responses, and message storage is bounded and thread-safe with regression coverage.
Reproducible message-store benchmark
benchmarks/*, .github/workflows/benchmarks.yml, tests/test_benchmark.py
A legacy-versus-optimized benchmark writes JSON evidence, validates throughput regression against 15%, uploads artifacts, and tests the benchmark result schema.
CI scanning and quality gates
.github/workflows/codeql.yml, .github/workflows/security-supply-chain.yml, .github/workflows/trojanchat-hygiene.yml
CodeQL scanning is added, Trivy findings fail workflows, Bandit and Ruff checks are enforced, coverage requires 90%, and pip-audit runs directly with JSON output.
Release evidence and readiness documentation
CHANGELOG.MD, README.md, docs/AUDIT.md, docs/PRODUCTION_CHECKLIST.md
Release notes, engineering evidence, audit findings, residual risks, and production verification requirements are documented.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 19.05% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the PR’s main themes: evidence-backed benchmarking, bounded chat storage, and stricter CI/security gates.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch agent/research-evidence-hardening

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 47b02bb284

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +21 to +23
configured_limit = max_messages or int(os.getenv("CHAT_HISTORY_LIMIT", "10000"))
if configured_limit < 1:
raise ValueError("max_messages must be positive")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reject zero max_messages values

When a caller explicitly passes max_messages=0, the or expression replaces it with the environment/default value (for example, 10,000), so the invalid capacity is silently accepted instead of reaching the positivity validation. This contradicts the constructor's stated positive-capacity contract and makes configuration mistakes in programmatic callers unexpectedly create a large store; distinguish None from an explicit zero before selecting the environment default.

Useful? React with 👍 / 👎.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
.github/workflows/security-supply-chain.yml (1)

58-62: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Ensure the SARIF report is uploaded even if vulnerabilities are found.

Setting exit-code: "1" causes this step to fail if vulnerabilities are detected. By default, GitHub Actions skips subsequent steps if a previous step fails, meaning the SARIF report won't be uploaded to GitHub Code Scanning when it's most needed.

🛠️ Proposed fix
       - name: Upload SARIF
+        if: success() || failure()
         uses: github/codeql-action/upload-sarif@v4
         with:
           sarif_file: trivy-results.sarif
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/security-supply-chain.yml around lines 58 - 62, Update the
Upload SARIF step using github/codeql-action/upload-sarif@v4 to run even when
the preceding Trivy scan exits with code 1, by adding the workflow condition
that allows execution after failure. Preserve the existing sarif_file
configuration.
backend/services/chat_service.py (1)

47-56: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Generate the timestamp inside the lock to guarantee chronological ordering.

Currently, the timestamp is generated before acquiring the lock. If multiple threads send messages concurrently, they might acquire the lock out of order relative to their generated timestamps. Generating the message dictionary inside the critical section ensures that the stored list order strictly matches the timestamps.

🛡️ Proposed fix
-        message = {
-            "id": self._generate_message_id(),
-            "username": username,
-            "content": content,
-            "timestamp": self._timestamp(),
-        }
-
         # In-memory storage (can replace with Firebase/Postgres/etc.)
         with self._lock:
+            message = {
+                "id": self._generate_message_id(),
+                "username": username,
+                "content": content,
+                "timestamp": self._timestamp(),
+            }
             self.messages.append(message)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/services/chat_service.py` around lines 47 - 56, Move message
construction, including the _timestamp() call, inside the self._lock critical
section in the message-sending method. Keep _generate_message_id(), username,
and content assignment intact, then append the fully constructed message while
holding the lock so storage order matches timestamp generation order.
🧹 Nitpick comments (2)
.github/workflows/codeql.yml (1)

22-22: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Disable credential persistence for checkout.

By default, actions/checkout persists the GitHub token in the local git config. For workflows that don't need to push changes back to the repository (especially analysis and security workflows), it is a security best practice to disable this to prevent potential token leakage.

🛡️ Proposed fix
-      - uses: actions/checkout@v4
+      - uses: actions/checkout@v4
+        with:
+          persist-credentials: false
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/codeql.yml at line 22, Update the actions/checkout step in
the CodeQL workflow to disable credential persistence by configuring
persist-credentials as false, while leaving the existing checkout behavior
unchanged.

Source: Linters/SAST tools

backend/routes/chat_routes.py (1)

52-59: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Use FastAPI's Query parameter for built-in validation.

Instead of manually validating the limit inside the function body, you can use FastAPI's Query to enforce boundaries. This is more idiomatic and automatically returns a 422 with a structured error if the input is out of bounds.

♻️ Proposed refactor

Update the function signature and remove the manual check:

+from fastapi import Query
+
-async def get_message_history(limit: int = 50):
+async def get_message_history(limit: int = Query(50, ge=1, le=200)):
     """
     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 HTTPException:
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/routes/chat_routes.py` around lines 52 - 59, Update the chat route
handler’s limit parameter to use FastAPI’s Query with minimum 1 and maximum 200
constraints, then remove the manual range check while preserving
get_messages(limit=limit) and existing exception handling.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/trojanchat-hygiene.yml:
- Around line 110-114: The Upload dependency audit step is skipped when Enforce
dependency audit fails, preventing access to the generated report. Update the
Upload dependency audit step to run regardless of the preceding pip-audit result
while preserving its existing artifact configuration.
- Around line 32-35: Split the combined “Run blocking Ruff and Bandit checks”
workflow step into separate Ruff and Bandit steps, preserving each command and
its arguments. Ensure both checks execute independently so a non-zero Ruff
result does not prevent the Bandit command from running.

In `@backend/services/chat_service.py`:
- Around line 21-23: Update the max_messages fallback in ChatService
initialization to distinguish an explicit 0 from an omitted value by checking
against None rather than using truthiness. Preserve the environment-based
default only when max_messages is None, so the existing configured_limit
validation rejects zero and other values below one.

In `@CHANGELOG.MD`:
- Around line 23-28: Restore the corrupted UTF-8 characters across the
documented Markdown sites: replace mojibake em-dashes and right-arrows in
CHANGELOG.MD lines 23-28, the em-dash in README.md line 52, the rocket emoji in
README.md line 72, and the directory-tree box-drawing characters in README.md
lines 101-114; replace the corrupted quotes in docs/AUDIT.md line 21 with proper
smart quotes. Preserve all surrounding text and formatting.

In `@requirements.txt`:
- Line 14: Update the PyJWT dependency constraint in requirements.txt from
>=2.12.0 to >=2.13.0, or pin it to an equivalent vetted patched release, so
vulnerable versions cannot be resolved.

In `@tests/test_security.py`:
- Around line 7-10: Update the load_security fixture to remove app.core.security
from sys.modules via monkeypatch.delitem instead of sys.modules.pop, ensuring
pytest restores the original module after teardown while preserving the existing
reload behavior.

---

Outside diff comments:
In @.github/workflows/security-supply-chain.yml:
- Around line 58-62: Update the Upload SARIF step using
github/codeql-action/upload-sarif@v4 to run even when the preceding Trivy scan
exits with code 1, by adding the workflow condition that allows execution after
failure. Preserve the existing sarif_file configuration.

In `@backend/services/chat_service.py`:
- Around line 47-56: Move message construction, including the _timestamp() call,
inside the self._lock critical section in the message-sending method. Keep
_generate_message_id(), username, and content assignment intact, then append the
fully constructed message while holding the lock so storage order matches
timestamp generation order.

---

Nitpick comments:
In @.github/workflows/codeql.yml:
- Line 22: Update the actions/checkout step in the CodeQL workflow to disable
credential persistence by configuring persist-credentials as false, while
leaving the existing checkout behavior unchanged.

In `@backend/routes/chat_routes.py`:
- Around line 52-59: Update the chat route handler’s limit parameter to use
FastAPI’s Query with minimum 1 and maximum 200 constraints, then remove the
manual range check while preserving get_messages(limit=limit) and existing
exception handling.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 79f4d4f5-9d0e-484c-a979-acdc325c1b40

📥 Commits

Reviewing files that changed from the base of the PR and between a9ce79b and 47b02bb.

📒 Files selected for processing (20)
  • .github/workflows/benchmarks.yml
  • .github/workflows/codeql.yml
  • .github/workflows/security-supply-chain.yml
  • .github/workflows/trojanchat-hygiene.yml
  • CHANGELOG.MD
  • README.md
  • app/core/security.py
  • backend/api.py
  • backend/config.py
  • backend/routes/chat_routes.py
  • backend/services/chat_service.py
  • benchmarks/benchmark_report.md
  • benchmarks/latest.json
  • benchmarks/run_benchmark.py
  • docs/AUDIT.md
  • docs/PRODUCTION_CHECKLIST.md
  • requirements.txt
  • tests/test_backend_hardening.py
  • tests/test_benchmark.py
  • tests/test_security.py

Comment on lines +32 to +35
- 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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Separate Ruff and Bandit into distinct steps.

Because GitHub Actions runs shell scripts with set -e by default, if ruff check finds issues and returns a non-zero exit code, the bandit command will not execute. Splitting these into two steps ensures both tools run and report their findings regardless of the other's status.

🛠️ Proposed fix
-      - 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: Run blocking Ruff 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
+      - name: Run blocking Bandit checks
+        if: success() || failure()
+        run: 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
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- 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: Run blocking Ruff 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
- name: Run blocking Bandit checks
if: success() || failure()
run: 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
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/trojanchat-hygiene.yml around lines 32 - 35, Split the
combined “Run blocking Ruff and Bandit checks” workflow step into separate Ruff
and Bandit steps, preserving each command and its arguments. Ensure both checks
execute independently so a non-zero Ruff result does not prevent the Bandit
command from running.

Comment on lines +110 to 114
- 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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Ensure the dependency audit report is uploaded on failure.

If pip-audit detects vulnerabilities, it returns a non-zero exit code which fails the step. As a result, the subsequent Upload dependency audit step will be skipped, and the JSON report won't be available for review.

🛠️ Proposed fix
       - name: Upload dependency audit
+        if: success() || failure()
         uses: actions/upload-artifact@v4
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- 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
- name: Enforce dependency audit
run: |
pip-audit -r requirements.txt --progress-spinner off --format json --output pip-audit.json
- name: Upload dependency audit
if: success() || failure()
uses: actions/upload-artifact@v4
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/trojanchat-hygiene.yml around lines 110 - 114, The Upload
dependency audit step is skipped when Enforce dependency audit fails, preventing
access to the generated report. Update the Upload dependency audit step to run
regardless of the preceding pip-audit result while preserving its existing
artifact configuration.

Comment on lines +21 to +23
configured_limit = max_messages or int(os.getenv("CHAT_HISTORY_LIMIT", "10000"))
if configured_limit < 1:
raise ValueError("max_messages must be positive")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Fix fallback logic for max_messages.

Using or for default fallback treats 0 as falsy. If a caller explicitly instantiates ChatService(max_messages=0), it will bypass the ValueError check and silently fall back to the environment default (10,000) instead of being rejected. Check against None explicitly.

🐛 Proposed fix
-        configured_limit = max_messages or int(os.getenv("CHAT_HISTORY_LIMIT", "10000"))
+        configured_limit = max_messages if max_messages is not None else int(os.getenv("CHAT_HISTORY_LIMIT", "10000"))
         if configured_limit < 1:
             raise ValueError("max_messages must be positive")
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
configured_limit = max_messages or int(os.getenv("CHAT_HISTORY_LIMIT", "10000"))
if configured_limit < 1:
raise ValueError("max_messages must be positive")
configured_limit = max_messages if max_messages is not None else int(os.getenv("CHAT_HISTORY_LIMIT", "10000"))
if configured_limit < 1:
raise ValueError("max_messages must be positive")
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/services/chat_service.py` around lines 21 - 23, Update the
max_messages fallback in ChatService initialization to distinguish an explicit 0
from an omitted value by checking against None rather than using truthiness.
Preserve the environment-based default only when max_messages is None, so the
existing configured_limit validation rejects zero and other values below one.

Comment thread CHANGELOG.MD
Comment on lines +23 to +28
- **CI/CD**: GitHub Actions updated — `actions/checkout@v3→v4`, `actions/setup-python@v4→v5`, Python `3.11→3.12`
- **CI/CD**: Fixed `backend/requirements.txt` reference (created missing file) and corrected test runner path
- **requirements.txt**: Pinned minimum versions for all Python dependencies; added `pydantic-settings>=2.5.0`, `fastapi>=0.115.0`, `uvicorn>=0.30.0`, `httpx>=0.27.0`, `pytest-asyncio>=0.24.0`
- **Pydantic v2**: Migrated `BaseSettings` import in `app/core/app/core/config.py` from `pydantic` to `pydantic_settings` package
- **Deprecation fixes**: Replaced `datetime.utcnow()` (deprecated in Python 3.12) with `datetime.now(timezone.utc)` in `mcp_adapter.py`, `app/core/security.py`, and `backend/services/chat_service.py`
- **Node.js (server/)**: Upgraded dependencies `express@4.18→4.21`, `socket.io@4.7→4.8`, `mongoose@7→8`, `dotenv@16.0→16.4`, `jest@29.5→29.7`, `supertest@6→7`, `nodemon@2→3`
- **Node.js (server/)**: Upgraded dependencies — `express@4.18→4.21`, `socket.io@4.7→4.8`, `mongoose@7→8`, `dotenv@16.0→16.4`, `jest@29.5→29.7`, `supertest@6→7`, `nodemon@2→3`

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Fix character encoding corruption (mojibake).

Several Markdown files contain text corrupted by a character encoding mismatch (e.g., UTF-8 bytes interpreted as Windows-1252), resulting in artifacts like —, →, 🚀, and “.

  • CHANGELOG.MD#L23-L28: restore proper em-dashes and right-arrows (, ).
  • README.md#L52-L52: fix the em-dash in microbenchmark—not.
  • README.md#L72-L72: fix the rocket emoji (🚀).
  • README.md#L101-L114: fix the Unicode box-drawing characters for the directory tree.
  • docs/AUDIT.md#L21-L21: fix the smart quotes (, ).
📍 Affects 3 files
  • CHANGELOG.MD#L23-L28 (this comment)
  • README.md#L52-L52
  • README.md#L72-L72
  • README.md#L101-L114
  • docs/AUDIT.md#L21-L21
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@CHANGELOG.MD` around lines 23 - 28, Restore the corrupted UTF-8 characters
across the documented Markdown sites: replace mojibake em-dashes and
right-arrows in CHANGELOG.MD lines 23-28, the em-dash in README.md line 52, the
rocket emoji in README.md line 72, and the directory-tree box-drawing characters
in README.md lines 101-114; replace the corrupted quotes in docs/AUDIT.md line
21 with proper smart quotes. Preserve all surrounding text and formatting.

Comment thread requirements.txt
redis>=5.2.0
prometheus-client>=0.21.0
python-jose[cryptography]>=3.3.0
PyJWT>=2.12.0

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Check PyPI for the latest version of PyJWT and query security advisories.

# Check PyPI for the latest version
curl -s https://pypi.org/pypi/PyJWT/json | jq -r '"Latest PyJWT version on PyPI: " + .info.version'

# Check for security advisories
gh api graphql -f query='
{
  securityVulnerabilities(first: 5, ecosystem: PIP, package: "PyJWT") {
    nodes {
      advisory {
        summary
        severity
        publishedAt
      }
      vulnerableVersionRange
      firstPatchedVersion {
        identifier
      }
    }
  }
}'

Repository: CoreyLeath-code/TrojanChat

Length of output: 1613


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== files mentioning PyJWT / PyJWKClient ==\n'
rg -n --hidden --glob '!**/.git/**' 'PyJWT|PyJWKClient|jwt\.' .

printf '\n== requirements files ==\n'
git ls-files '*requirements*.txt' '*requirements*.in' 'pyproject.toml' 'Pipfile*' 'poetry.lock' 'uv.lock' 'Pipfile.lock' 'package-lock.json' 'pnpm-lock.yaml' 'yarn.lock' | sed 's#^`#-` #'

printf '\n== requirements.txt around PyJWT ==\n'
if [ -f requirements.txt ]; then
  nl -ba requirements.txt | sed -n '1,40p'
fi

Repository: CoreyLeath-code/TrojanChat

Length of output: 831


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
import urllib.request, json, ssl
urls = [
    "https://pypi.org/pypi/PyJWT/json",
]
for url in urls:
    with urllib.request.urlopen(url, context=ssl.create_default_context(), timeout=20) as r:
        data = json.load(r)
    print("latest:", data["info"]["version"])
PY

Repository: CoreyLeath-code/TrojanChat

Length of output: 2758


Raise the PyJWT floor to a patched release
PyJWT>=2.12.0 still allows versions covered by published advisories. Bump this to >=2.13.0 (or pin a vetted release) so the dependency file can’t resolve to known vulnerable builds.

🧰 Tools
🪛 OSV Scanner (2.4.0)

[HIGH] 14-14: pyjwt 2.12.0: undefined

(PYSEC-2026-175)


[HIGH] 14-14: pyjwt 2.12.0: undefined

(PYSEC-2026-176)


[HIGH] 14-14: pyjwt 2.12.0: undefined

(PYSEC-2026-177)


[HIGH] 14-14: pyjwt 2.12.0: undefined

(PYSEC-2026-178)


[HIGH] 14-14: pyjwt 2.12.0: undefined

(PYSEC-2026-179)


[HIGH] 14-14: pyjwt 2.12.0: PyJWKClient: missing scheme allowlist enables CVE-2024-21643-class SSRF + token forgery via file://, ftp://, data: schemes

(GHSA-993g-76c3-p5m4)


[HIGH] 14-14: pyjwt 2.12.0: PyJWKClient unbounded JWKS endpoint requests via attacker-controlled kid values (DoS)

(GHSA-fhv5-28vv-h8m8)


[HIGH] 14-14: pyjwt 2.12.0: PyJWT: Algorithm allow-list bypass when decoding with PyJWK / PyJWKClient keys

(GHSA-jq35-7prp-9v3f)


[HIGH] 14-14: pyjwt 2.12.0: PyJWT: Unauthenticated DoS via unbounded Base64URL decoding of unused payload segment in b64=false detached JWS

(GHSA-w7vc-732c-9m39)


[HIGH] 14-14: pyjwt 2.12.0: PyJWT: Public-key JWK accepted as HMAC secret enables forged HS256 tokens when mixed families are allowed

(GHSA-xgmm-8j9v-c9wx)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@requirements.txt` at line 14, Update the PyJWT dependency constraint in
requirements.txt from >=2.12.0 to >=2.13.0, or pin it to an equivalent vetted
patched release, so vulnerable versions cannot be resolved.

Source: Linters/SAST tools

Comment thread tests/test_security.py
Comment on lines +7 to +10
def load_security(monkeypatch):
monkeypatch.setenv("SECRET_KEY", "test-only-secret-that-is-at-least-32-bytes")
sys.modules.pop("app.core.security", None)
return importlib.import_module("app.core.security")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Use monkeypatch.delitem to prevent cross-test state leakage.

Using sys.modules.pop permanently modifies the test runner's sys.modules. Any tests running after this one in the same process will inherit the reloaded module containing the test-only secret. By using monkeypatch.delitem, pytest will automatically restore the original module during teardown.

🐛 Proposed fix
 def load_security(monkeypatch):
     monkeypatch.setenv("SECRET_KEY", "test-only-secret-that-is-at-least-32-bytes")
-    sys.modules.pop("app.core.security", None)
+    monkeypatch.delitem(sys.modules, "app.core.security", raising=False)
     return importlib.import_module("app.core.security")
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def load_security(monkeypatch):
monkeypatch.setenv("SECRET_KEY", "test-only-secret-that-is-at-least-32-bytes")
sys.modules.pop("app.core.security", None)
return importlib.import_module("app.core.security")
def load_security(monkeypatch):
monkeypatch.setenv("SECRET_KEY", "test-only-secret-that-is-at-least-32-bytes")
monkeypatch.delitem(sys.modules, "app.core.security", raising=False)
return importlib.import_module("app.core.security")
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_security.py` around lines 7 - 10, Update the load_security fixture
to remove app.core.security from sys.modules via monkeypatch.delitem instead of
sys.modules.pop, ensuring pytest restores the original module after teardown
while preserving the existing reload behavior.

@CoreyLeath-code
CoreyLeath-code merged commit e4c8a41 into main Jul 18, 2026
20 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant