Skip to content

fix: security hardening — XSS, push spoofing, metadata leaks, deletion bugs#98

Merged
GeiserX merged 2 commits intomainfrom
fix/security-hardening
Mar 31, 2026
Merged

fix: security hardening — XSS, push spoofing, metadata leaks, deletion bugs#98
GeiserX merged 2 commits intomainfrom
fix/security-hardening

Conversation

@GeiserX
Copy link
Copy Markdown
Owner

@GeiserX GeiserX commented Mar 31, 2026

Summary

Addresses 5 findings from a security hardening review.

High: Stored XSS via linkifyText()

escapeHtml() converts " to ", but when the regex inserts the URL into href="$1", the browser decodes " back to ", allowing attribute breakout. Fix: percent-encode %22, %27, %3C, %3E in the href context while keeping HTML-escaped display text.

Medium: /internal/push IP spoofing

Was accepting all RFC1918 ranges (172.*, 10.*, 192.168.*). Behind Docker/LAN proxies, external requests arrive from these IPs. Now restricted to loopback only (127.0.0.1, ::1) — SQLite push is always same-container.

Medium: Stats/folders metadata leak to restricted viewers

/api/stats returned per_chat_message_counts for ALL chats, and /api/folders returned unfiltered chat counts. Now both filter by get_user_chat_ids().

Medium: Cross-chat message deletion

When Telegram sends a delete event without chat_id, the listener resolved it via get_chat_id_for_message() which returns the first arbitrary match. Now uses delete_message_by_id_any_chat() which correctly handles all matching chats.

Low: Broken reactions sequence reset

_reset_reactions_sequence() referenced self.db_manager.db_type which doesn't exist. Fixed to use self.db_manager._is_sqlite.

Test plan

  • Full test suite passes (167 tests)
  • Ruff lint + format clean

- High: fix stored XSS in linkifyText() — HTML entities in URLs could
  break out of href attribute; now percent-encode quotes in href context
- Medium: restrict /internal/push to loopback only (127.0.0.1, ::1);
  private LAN ranges are spoofable behind reverse proxies
- Medium: filter /api/stats per_chat_message_counts and /api/folders
  chat_count by user's allowed_chat_ids to prevent metadata leaks
- Medium: use delete_message_by_id_any_chat() for deletions without
  chat_id to avoid cross-chat message ID collisions
- Low: fix _reset_reactions_sequence() using nonexistent db_type
  property; use _is_sqlite instead
@github-actions
Copy link
Copy Markdown

🐳 Dev images published!

  • drumsergio/telegram-archive:dev
  • drumsergio/telegram-archive-viewer:dev

The dev/test instance will pick up these changes automatically (Portainer GitOps).

To test locally:

docker pull drumsergio/telegram-archive:dev
docker pull drumsergio/telegram-archive-viewer:dev

- XSS: handle raw " and ' in URLs (escapeHtml doesn't encode quotes)
- Stats: fix JSON string keys vs int user_chat_ids type mismatch
- Stats: remove media_files/total_size_mb for restricted users
- Deletion: resolve chat_id from DB first, apply rate limiter, notify viewer
- Deletion: skip ambiguous message IDs found in multiple chats
- Folders: filter out folders with 0 visible chats for restricted users
- Push endpoint: accept private IPs for split-container SQLite mode
@github-actions
Copy link
Copy Markdown

🐳 Dev images published!

  • drumsergio/telegram-archive:dev
  • drumsergio/telegram-archive-viewer:dev

The dev/test instance will pick up these changes automatically (Portainer GitOps).

To test locally:

docker pull drumsergio/telegram-archive:dev
docker pull drumsergio/telegram-archive-viewer:dev

@GeiserX GeiserX merged commit 0897f54 into main Mar 31, 2026
7 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