diff --git a/config.ini.example b/config.ini.example index 3949490..8f002cc 100644 --- a/config.ini.example +++ b/config.ini.example @@ -202,24 +202,8 @@ radio_probe_fail_threshold = 3 # true: send alert email in addition to logging radio_zombie_alert_enabled = false -<<<<<<< HEAD -======= -# SMTP security note: by default the bot blocks SMTP hosts that resolve to -# any private, loopback, link-local, or reserved address to prevent SSRF. -# Blocked ranges (per RFC): -# 10.0.0.0/8 RFC 1918 — private -# 172.16.0.0/12 RFC 1918 — private -# 192.168.0.0/16 RFC 1918 — private -# 127.0.0.0/8 RFC 1122 — loopback -# 169.254.0.0/16 RFC 3927 — link-local -# 100.64.0.0/10 RFC 6598 — shared address space (CGN) -# ::1/128 RFC 4291 — IPv6 loopback -# fc00::/7 RFC 4193 — IPv6 unique local (ULA) -# fe80::/10 RFC 4291 — IPv6 link-local -# If you run a local SMTP relay (e.g. Postfix on localhost), enable -# "Allow local SMTP host" in the web viewer → Settings → Notifications. - ->>>>>>> 38d040a (security: SSRF hardening, log injection sanitization, and allow_local_smtp) +# SMTP hosts resolving to private/loopback/reserved ranges are blocked by default (SSRF guard). +# If you intentionally use local SMTP, enable "Allow local SMTP host" in Notifications. # Alert email recipients for zombie detection (comma-separated addresses). # If empty, falls back to the nightly maintenance email recipients. # Set this to a separate on-call address if needed. diff --git a/docs/integration/kg7qin-pr-integration-log.md b/docs/integration/kg7qin-pr-integration-log.md new file mode 100644 index 0000000..4e87262 --- /dev/null +++ b/docs/integration/kg7qin-pr-integration-log.md @@ -0,0 +1,72 @@ +# KG7QIN PR Integration Log + +Branch: `integration/kg7qin` +Base: `origin/dev` + +## Policy Constraints +- Exclude `!plugins` command from all integrations. +- Treat command behaviors that create multi-message output as regressions. +- Keep `config.ini.example` and related config templates brief; move long-form docs to `docs/`. + +## Inventory and Initial Classification + +### Stage 1 +- PR #138 + - `accept`: `aa94d23`, `69675ac`, `0be6004`, `6a627c2`, `975d744`, `0cbd764` + - `accept-with-edit`: none + - `drop`: lint-only/shared-sync commits already covered elsewhere +- PR #140 + - `accept`: `38d040a`, `04eba0a` + - `accept-with-edit`: none + - `drop`: broad lint/mypy sweep commit (`7af161e`) unless needed by gate +- PR #147 + - `accept`: `df66761` + - `accept-with-edit`: `ca67ec4` (only if required to restore expected flood-scope/public-channel behavior) + - `drop`: test/lint hygiene commits unless required by gate +- PR #145 + - `accept`: `2272b86` + - `accept-with-edit`: none + - `drop`: broad ruff/sync commits unless required by gate +- PR #155 + - `accept`: `2b896c6` (delta-only post-rebase compatibility) + - `drop`: duplicated #138 history +- PR #156 + - `accept`: `5ac7ae0` (delta-only post-rebase compatibility) + - `drop`: duplicated #147 history + +### Stage 2 +- PR #139 + - `accept`: `7450ac3` + - `accept-with-edit`: `86264d6` docs portions only if concise for config templates + - `drop`: duplicated lint/sync commits +- PR #149 + - `accept`: `88e8fa4`, `f6e1924` + - `accept-with-edit`: none + - `drop`: duplicated lint/sync commits unless required by gate +- PR #148 + - `accept`: `4a96f7f` (partial: `--show-config`, `--show-config-json`, `/admin/config`) + - `accept-with-edit`: `655da24` (keep `!status`, drop `!plugins`, reject any multi-message command behavior) + - `drop`: `9310a38`, `4dd9834` if they are mostly plugins-focused +- PR #154 + - `accept`: `a02c15f` + - `accept-with-edit`: none + - `drop`: none initially +- PR #141 + - `accept-with-edit`: `a8edb80` (preserve single-message behavior) + - `drop`: duplicated lint/sync commits unless required +- PR #157 + - `accept`: `6eb8001` if needed after #148 partial integration + - `drop`: duplicated #148 history +- PR #158 + - `accept`: `b2dddc5` if needed after #149 integration + - `drop`: duplicated #149 history + +### Stage 3 +- PR #142 + - `accept-with-edit`: `a85e6ac`, `f4df680`, `9683abb`, `86b2f53`, `21eed98` (only tests/coverage updates that still reflect current code and policy) + - `drop`: duplicated lint/sync commits +- PR #159 + - `accept`: `6302b07` (targeted ruff/mypy compliance) + +## Execution Records +- Pending implementation. diff --git a/modules/web_viewer/app.py b/modules/web_viewer/app.py index 4165332..97a5497 100644 --- a/modules/web_viewer/app.py +++ b/modules/web_viewer/app.py @@ -256,13 +256,8 @@ class BotDataViewer: config.read(config_path) return config -<<<<<<< HEAD def _get_version_info(self) -> dict[str, Optional[str]]: """Get version info for footer from shared runtime resolver.""" -======= - def _get_version_info(self) -> dict[str, str | None]: - """Get version info for footer via centralized version resolver. Never raises.""" ->>>>>>> 5ac7ae0 (fix: post-rebase compatibility fixes for #147 (stability hardening)) info = resolve_runtime_version(self.bot_root) return { "tag": info.get("tag"), diff --git a/tests/test_core.py b/tests/test_core.py index d31155f..641b158 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -282,7 +282,6 @@ class TestLoopExceptionHandler: mock_loop.default_exception_handler.assert_called_once_with(ctx) -<<<<<<< HEAD # --------------------------------------------------------------------------- # _probe_radio_health (PR4 — zombie-connection detection) # --------------------------------------------------------------------------- @@ -539,5 +538,3 @@ async def _make_coro_async(value): def _make_coro(value): """Return a coroutine that immediately resolves to *value*.""" return _make_coro_async(value) -======= ->>>>>>> 38d040a (security: SSRF hardening, log injection sanitization, and allow_local_smtp) diff --git a/tests/test_scheduler_logic.py b/tests/test_scheduler_logic.py index cbd5557..80583dd 100644 --- a/tests/test_scheduler_logic.py +++ b/tests/test_scheduler_logic.py @@ -1308,7 +1308,6 @@ class TestCollectEmailStats: assert result.get("contacts_24h") == 10 assert result.get("contacts_new_24h") == 3 -<<<<<<< HEAD # --------------------------------------------------------------------------- # _send_interval_advert_async (PR2 fix — Event-based error detection) @@ -1461,7 +1460,6 @@ class TestSendScheduledMessageAsyncTimeout: assert call_args_list, "logger.error was never called" logged = str(call_args_list[0]) assert "TimeoutError" in logged -======= # --------------------------------------------------------------------------- # SSRF guard — SMTP host validation in email-sending methods # --------------------------------------------------------------------------- @@ -1558,4 +1556,3 @@ class TestZombieAlertEmailSsrfGuard: sched.send_zombie_alert_email(fail_count=5, threshold=3, interval=60) logged = str(sched.bot.logger.error.call_args_list) assert "private" not in logged.lower() and "reserved" not in logged.lower() ->>>>>>> 38d040a (security: SSRF hardening, log injection sanitization, and allow_local_smtp) diff --git a/tests/test_web_viewer.py b/tests/test_web_viewer.py index 58610ae..150336d 100644 --- a/tests/test_web_viewer.py +++ b/tests/test_web_viewer.py @@ -2970,7 +2970,6 @@ class TestDbPathResolutionFromConfigDir: ) -<<<<<<< HEAD class TestRadioDebugConfig: """Tests for GET/POST /api/config/radio-debug endpoints.""" @@ -3040,7 +3039,6 @@ class TestRadioDebugConfig: data = resp.get_json() assert data["success"] is True assert data["op_id"] is None -======= # =========================================================================== # Security: Restore endpoint path traversal prevention (GAP W1) # =========================================================================== @@ -3188,4 +3186,3 @@ class TestFeedPreviewSecurity: ) mock_veu.assert_called() assert resp.status_code == 400 ->>>>>>> 38d040a (security: SSRF hardening, log injection sanitization, and allow_local_smtp)