Skip to content

Security: fix chat XSS and harden login/request-size/secret-key - #50

Merged
vl3c merged 1 commit into
mainfrom
security/xss-and-hardening
Jul 11, 2026
Merged

Security: fix chat XSS and harden login/request-size/secret-key#50
vl3c merged 1 commit into
mainfrom
security/xss-and-hardening

Conversation

@vl3c

@vl3c vl3c commented Jul 11, 2026

Copy link
Copy Markdown
Owner

Summary

Security follow-up to the PR #49 codebase review. Fixes the one High-severity verified vulnerability (stored/reflected XSS in AI chat rendering) plus three hardening gaps found during the survey.

Base branch: targets refactor/architecture-cleanup (PR #49), not main — the XSS fix touches chat_ui_manager.py, which only exists on that branch. Merge after (or into) PR #49.

XSS in chat rendering (High)

AI output is attacker-influenceable via prompt injection (workspace text, tool results), and the custom markdown parser did no HTML escaping before assigning to innerHTML.

  • markdown_parser.py now HTML-escapes source text before any markdown transformation, so raw <script> / <img onerror=...> can never reach the innerHTML sinks — only parser-emitted tags survive.
  • Link URLs pass through a scheme allowlist (http/https/mailto + relative); javascript:, data:, vbscript:, and control-char tricks (java\tscript:) are neutralized to plain text.
  • chat_ui_manager.py escapes sender/content in the error-fallback innerHTML path.

Login rate-limit bypass (Medium)

  • Rate limiting now keys on request.remote_addr instead of the client-spoofable X-Forwarded-For header.
  • Added a global failed-attempt ceiling (50 failures / 5-min rolling window) that blocks brute force from distributed or header-spoofed sources, independent of per-IP cooldown.

No request size limit / client-only image caps (Medium)

  • Set MAX_CONTENT_LENGTH (80 MB) with a JSON 413 error handler.
  • Enforce server-side image caps (max 5 images, ~20 MB base64 each) on both /send_message and /send_message_stream — previously enforced only client-side.

Ephemeral SECRET_KEY in deployment (Low)

  • Warn prominently when SECRET_KEY is unset in a deployed environment (sessions won't survive restarts); random fallback retained, no hard-fail.

Test plan

  • 13 new markdown XSS regression tests — raw HTML escaped, javascript:/mixed-case/control-char/data: links neutralized, safe links & normal markdown still render, code blocks escaped.
  • New test_login_and_hardening.py — the login flow had zero tests before. Covers correct/incorrect PIN, per-IP cooldown, X-Forwarded-For spoof resistance, global ceiling lockout, image count/size rejection, MAX_CONTENT_LENGTH + JSON 413.
  • Server tests: 1293 passed, 13 skipped, 0 failures
  • Client tests: 2733 passed, 0 failures, 0 errors
  • mypy + ruff clean on all changed files

Not included (documented for follow-up)

Concurrency thread-safety of shared provider/conversation state, Selenium driver locking, webdriver_manager test coverage, workspace_manager.py print()→logging, and friendlier streaming error messages — all latent/lower-priority items from the same survey.

XSS (client):
- markdown_parser.py now HTML-escapes source text before any markdown
  transformation, so raw <script>/<img onerror> in AI output can never
  reach the innerHTML sinks; only parser-emitted tags survive
- Added URL scheme allowlist (http/https/mailto + relative) for links;
  javascript:/data:/vbscript: and control-char tricks are neutralized to
  plain text
- chat_ui_manager.py escapes sender/content in the error-fallback sink

Server hardening:
- Login rate limit keys on request.remote_addr instead of the spoofable
  X-Forwarded-For; added a global failed-attempt ceiling (50 / 5 min)
  that blocks brute force from distributed/spoofed sources
- Set MAX_CONTENT_LENGTH (80 MB) with a JSON 413 handler; enforce
  server-side image caps (max 5, 20 MB base64 each) on both send routes
- Warn when SECRET_KEY is unset in a deployed environment

Tests:
- 13 markdown XSS regression tests; new test_login_and_hardening.py
  covering login, per-IP + global rate limiting, XFF spoof resistance,
  and image validation
- Server: 1293 passed; client: 2733 passed; mypy and ruff clean
Base automatically changed from refactor/architecture-cleanup to main July 11, 2026 21:54
@vl3c
vl3c merged commit 5dc14e3 into main Jul 11, 2026
1 check passed
@vl3c
vl3c deleted the security/xss-and-hardening branch July 11, 2026 21:54
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