feat: harden notification stack and switch logging selectors to icon grid
Notifications: - Add shared http_base, redact, and SSRF hardening modules - Refactor dispatcher, queue, receiver and per-provider clients (telegram, discord, email, matrix, ntfy, slack, webhook) to use the shared base, with bounded queue and redacted error logs - Tests for ssrf, redact, http_base, queue bounds, dispatcher aggregation, telegram media partition, email and matrix clients Frontend: - Settings: log level / log format selectors now use IconGridSelect with per-option icons and i18n descriptions - Minor providers page and entity-cache store updates Tooling: - Document code-review-graph MCP usage in CLAUDE.md - Ignore .code-review-graph/, register .mcp.json
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
"""Email client header-injection / address-validation regression tests."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from notify_bridge_core.notifications.email.client import (
|
||||
EmailClient,
|
||||
SmtpConfig,
|
||||
_strip_header,
|
||||
_validate_email,
|
||||
_to_html,
|
||||
)
|
||||
|
||||
|
||||
def test_strip_header_removes_crlf() -> None:
|
||||
out = _strip_header("Subject\r\nBcc: attacker@example.com")
|
||||
assert "\r" not in out
|
||||
assert "\n" not in out
|
||||
# The injected "Bcc:" line is folded to a single header line; the SMTP
|
||||
# server will treat it as part of the subject text, not a header.
|
||||
assert "Bcc:" in out # value preserved as plain text
|
||||
|
||||
|
||||
def test_strip_header_removes_bare_lf() -> None:
|
||||
out = _strip_header("Hello\nWorld")
|
||||
assert "\n" not in out
|
||||
|
||||
|
||||
def test_strip_header_handles_non_string() -> None:
|
||||
assert _strip_header(None) == ""
|
||||
|
||||
|
||||
def test_validate_email_rejects_crlf() -> None:
|
||||
with pytest.raises(ValueError):
|
||||
_validate_email("user@example.com\r\nBcc: x@y")
|
||||
|
||||
|
||||
def test_validate_email_rejects_no_at() -> None:
|
||||
with pytest.raises(ValueError):
|
||||
_validate_email("not-an-email")
|
||||
|
||||
|
||||
def test_validate_email_rejects_empty() -> None:
|
||||
with pytest.raises(ValueError):
|
||||
_validate_email("")
|
||||
|
||||
|
||||
def test_validate_email_accepts_normal() -> None:
|
||||
assert _validate_email("user@example.com") == "user@example.com"
|
||||
|
||||
|
||||
def test_to_html_escapes_brackets() -> None:
|
||||
out = _to_html("<script>alert(1)</script>")
|
||||
assert "<script>" not in out
|
||||
assert "<script>" in out
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_send_returns_error_on_invalid_to() -> None:
|
||||
cfg = SmtpConfig(host="smtp.example.com", from_address="from@example.com")
|
||||
client = EmailClient(cfg)
|
||||
result = await client.send(
|
||||
to_email="user@example.com\r\nBcc: attacker@example.com",
|
||||
subject="hi",
|
||||
body_text="body",
|
||||
)
|
||||
assert result["success"] is False
|
||||
assert "Invalid email" in result["error"]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_send_returns_error_on_no_host() -> None:
|
||||
cfg = SmtpConfig(host="", from_address="from@example.com")
|
||||
client = EmailClient(cfg)
|
||||
result = await client.send("u@x.com", "s", "b")
|
||||
assert result["success"] is False
|
||||
Reference in New Issue
Block a user