From f829ed9ddd5a694ea44334b28814c2b5b45afd48 Mon Sep 17 00:00:00 2001 From: agessaman Date: Thu, 1 Oct 2026 01:02:33 -0700 Subject: [PATCH] test(characterization): pin packaging coverage and the web viewer route table The packaging test checks pyproject.toml against git ls-files: every directory under modules/ with Python files must be a regular package, and every tracked runtime data file must match a package-data glob, since include-package-data is off and a new subpackage or data file would otherwise silently miss the wheel. The endpoint inventory records all 104 routes with their methods and endpoint names, plus the status, content type, redirect target, script-src CSP (nonce normalized) and framing headers of anonymous requests with and without a password, and of anonymous mutating requests when locked. Endpoint names feed the CSP nonce allowlist and url_for, so a helper extraction that renames or reroutes anything shows up here. --- .../golden/web_endpoint_inventory.json | 1883 +++++++++++++++++ tests/characterization/golden_util.py | 2 +- .../test_bot_identity_matrix.py | 2 +- .../test_packaging_coverage.py | 69 + .../test_web_endpoint_inventory.py | 94 + 5 files changed, 2048 insertions(+), 2 deletions(-) create mode 100644 tests/characterization/golden/web_endpoint_inventory.json create mode 100644 tests/characterization/test_packaging_coverage.py create mode 100644 tests/characterization/test_web_endpoint_inventory.py diff --git a/tests/characterization/golden/web_endpoint_inventory.json b/tests/characterization/golden/web_endpoint_inventory.json new file mode 100644 index 0000000..7498dd5 --- /dev/null +++ b/tests/characterization/golden/web_endpoint_inventory.json @@ -0,0 +1,1883 @@ +{ + "/": { + "GET locked anon": { + "content_type": "text/html", + "csp_script_src": "script-src 'self' 'nonce-X' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "", + "status": 200, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "GET open": { + "content_type": "text/html", + "csp_script_src": "script-src 'self' 'nonce-X' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "", + "status": 200, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "endpoint": "index", + "methods": [ + "GET" + ] + }, + "/admin/config": { + "GET locked anon": { + "content_type": "text/html", + "csp_script_src": "script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "/login?next=/admin/config", + "status": 302, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "GET open": { + "content_type": "text/html", + "csp_script_src": "script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "", + "status": 200, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "endpoint": "admin_config", + "methods": [ + "GET" + ] + }, + "/api-explorer": { + "GET locked anon": { + "content_type": "text/html", + "csp_script_src": "script-src 'self' 'nonce-X' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "/login?next=/api-explorer", + "status": 302, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "GET open": { + "content_type": "text/html", + "csp_script_src": "script-src 'self' 'nonce-X' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "", + "status": 200, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "endpoint": "api_explorer", + "methods": [ + "GET" + ] + }, + "/api/admin/radio-offline-clear": { + "POST locked anon": { + "content_type": "application/json", + "csp_script_src": "script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "", + "status": 401, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "endpoint": "api_admin_radio_offline_clear", + "methods": [ + "POST" + ] + }, + "/api/admin/zombie-recover": { + "POST locked anon": { + "content_type": "application/json", + "csp_script_src": "script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "", + "status": 401, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "endpoint": "api_admin_zombie_recover", + "methods": [ + "POST" + ] + }, + "/api/banner-status": { + "GET locked anon": { + "content_type": "application/json", + "csp_script_src": "script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "", + "status": 200, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "GET open": { + "content_type": "application/json", + "csp_script_src": "script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "", + "status": 200, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "endpoint": "api_banner_status", + "methods": [ + "GET" + ] + }, + "/api/cache": { + "GET locked anon": { + "content_type": "application/json", + "csp_script_src": "script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "", + "status": 401, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "GET open": { + "content_type": "application/json", + "csp_script_src": "script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "", + "status": 200, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "endpoint": "api_cache", + "methods": [ + "GET" + ] + }, + "/api/channel-operations/": { + "endpoint": "api_get_operation_status", + "methods": [ + "GET" + ] + }, + "/api/channels": { + "POST locked anon": { + "content_type": "application/json", + "csp_script_src": "script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "", + "status": 401, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "endpoint": "api_create_channel", + "methods": [ + "POST" + ] + }, + "/api/channels/": { + "PUT locked anon": { + "content_type": "application/json", + "csp_script_src": "script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "", + "status": 401, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "endpoint": "api_update_channel", + "methods": [ + "PUT" + ] + }, + "/api/channels//feeds": { + "endpoint": "api_channel_feeds", + "methods": [ + "GET" + ] + }, + "/api/channels/stats": { + "GET locked anon": { + "content_type": "application/json", + "csp_script_src": "script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "", + "status": 401, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "GET open": { + "content_type": "application/json", + "csp_script_src": "script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "", + "status": 200, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "endpoint": "api_channel_stats", + "methods": [ + "GET" + ] + }, + "/api/channels/validate": { + "POST locked anon": { + "content_type": "application/json", + "csp_script_src": "script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "", + "status": 401, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "endpoint": "api_validate_channel", + "methods": [ + "POST" + ] + }, + "/api/config/logging": { + "POST locked anon": { + "content_type": "application/json", + "csp_script_src": "script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "", + "status": 401, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "endpoint": "api_config_logging_post", + "methods": [ + "POST" + ] + }, + "/api/config/maintenance": { + "POST locked anon": { + "content_type": "application/json", + "csp_script_src": "script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "", + "status": 401, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "endpoint": "api_config_maintenance_post", + "methods": [ + "POST" + ] + }, + "/api/config/notifications": { + "POST locked anon": { + "content_type": "application/json", + "csp_script_src": "script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "", + "status": 401, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "endpoint": "api_config_notifications_post", + "methods": [ + "POST" + ] + }, + "/api/config/notifications/test": { + "POST locked anon": { + "content_type": "application/json", + "csp_script_src": "script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "", + "status": 401, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "endpoint": "api_config_notifications_test", + "methods": [ + "POST" + ] + }, + "/api/config/radio-debug": { + "POST locked anon": { + "content_type": "application/json", + "csp_script_src": "script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "", + "status": 401, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "endpoint": "api_config_radio_debug_post", + "methods": [ + "POST" + ] + }, + "/api/config/radio-offline-alert": { + "POST locked anon": { + "content_type": "application/json", + "csp_script_src": "script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "", + "status": 401, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "endpoint": "api_config_radio_offline_alert_post", + "methods": [ + "POST" + ] + }, + "/api/config/radio-probe": { + "POST locked anon": { + "content_type": "application/json", + "csp_script_src": "script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "", + "status": 401, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "endpoint": "api_config_radio_probe_post", + "methods": [ + "POST" + ] + }, + "/api/config/zombie-alert": { + "POST locked anon": { + "content_type": "application/json", + "csp_script_src": "script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "", + "status": 401, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "endpoint": "api_config_zombie_alert_post", + "methods": [ + "POST" + ] + }, + "/api/connected_clients": { + "GET locked anon": { + "content_type": "application/json", + "csp_script_src": "script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "", + "status": 401, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "GET open": { + "content_type": "application/json", + "csp_script_src": "script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "", + "status": 200, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "endpoint": "api_connected_clients", + "methods": [ + "GET" + ] + }, + "/api/contact-detail": { + "GET locked anon": { + "content_type": "application/json", + "csp_script_src": "script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "", + "status": 400, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "GET open": { + "content_type": "application/json", + "csp_script_src": "script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "", + "status": 400, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "endpoint": "api_contact_detail", + "methods": [ + "GET" + ] + }, + "/api/contacts": { + "GET locked anon": { + "content_type": "application/json", + "csp_script_src": "script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "", + "status": 200, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "GET open": { + "content_type": "application/json", + "csp_script_src": "script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "", + "status": 200, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "endpoint": "api_contacts", + "methods": [ + "GET" + ] + }, + "/api/contacts/purge": { + "POST locked anon": { + "content_type": "application/json", + "csp_script_src": "script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "", + "status": 401, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "endpoint": "api_contacts_purge", + "methods": [ + "POST" + ] + }, + "/api/contacts/purge-preview": { + "GET locked anon": { + "content_type": "application/json", + "csp_script_src": "script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "", + "status": 401, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "GET open": { + "content_type": "application/json", + "csp_script_src": "script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "", + "status": 200, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "endpoint": "api_contacts_purge_preview", + "methods": [ + "GET" + ] + }, + "/api/dashboard/refresh": { + "POST locked anon": { + "content_type": "application/json", + "csp_script_src": "script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "", + "status": 401, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "endpoint": "api_dashboard_refresh", + "methods": [ + "POST" + ] + }, + "/api/dashboard/series": { + "GET locked anon": { + "content_type": "application/json", + "csp_script_src": "script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "", + "status": 200, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "GET open": { + "content_type": "application/json", + "csp_script_src": "script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "", + "status": 200, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "endpoint": "api_dashboard_series", + "methods": [ + "GET" + ] + }, + "/api/dashboard/summary": { + "GET locked anon": { + "content_type": "application/json", + "csp_script_src": "script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "", + "status": 503, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "GET open": { + "content_type": "application/json", + "csp_script_src": "script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "", + "status": 503, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "endpoint": "api_dashboard_summary", + "methods": [ + "GET" + ] + }, + "/api/dashboard/top": { + "GET locked anon": { + "content_type": "application/json", + "csp_script_src": "script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "", + "status": 200, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "GET open": { + "content_type": "application/json", + "csp_script_src": "script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "", + "status": 200, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "endpoint": "api_dashboard_top", + "methods": [ + "GET" + ] + }, + "/api/dashboard/windows": { + "GET locked anon": { + "content_type": "application/json", + "csp_script_src": "script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "", + "status": 200, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "GET open": { + "content_type": "application/json", + "csp_script_src": "script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "", + "status": 200, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "endpoint": "api_dashboard_windows", + "methods": [ + "GET" + ] + }, + "/api/database": { + "GET locked anon": { + "content_type": "application/json", + "csp_script_src": "script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "", + "status": 401, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "GET open": { + "content_type": "application/json", + "csp_script_src": "script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "", + "status": 200, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "endpoint": "api_database", + "methods": [ + "GET" + ] + }, + "/api/decode-path": { + "POST locked anon": { + "content_type": "application/json", + "csp_script_src": "script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "", + "status": 400, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "endpoint": "api_decode_path", + "methods": [ + "POST" + ] + }, + "/api/delete-contact": { + "POST locked anon": { + "content_type": "application/json", + "csp_script_src": "script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "", + "status": 401, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "endpoint": "api_delete_contact", + "methods": [ + "POST" + ] + }, + "/api/export/contacts": { + "GET locked anon": { + "content_type": "application/json", + "csp_script_src": "script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "", + "status": 401, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "GET open": { + "content_type": "application/json", + "csp_script_src": "script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "", + "status": 200, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "endpoint": "api_export_contacts", + "methods": [ + "GET" + ] + }, + "/api/export/paths": { + "GET locked anon": { + "content_type": "application/json", + "csp_script_src": "script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "", + "status": 401, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "GET open": { + "content_type": "application/json", + "csp_script_src": "script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "", + "status": 200, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "endpoint": "api_export_paths", + "methods": [ + "GET" + ] + }, + "/api/feeds": { + "GET locked anon": { + "content_type": "application/json", + "csp_script_src": "script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "", + "status": 401, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "GET open": { + "content_type": "application/json", + "csp_script_src": "script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "", + "status": 200, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "endpoint": "api_feeds", + "methods": [ + "GET" + ] + }, + "/api/feeds/": { + "PUT locked anon": { + "content_type": "application/json", + "csp_script_src": "script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "", + "status": 401, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "endpoint": "api_update_feed", + "methods": [ + "PUT" + ] + }, + "/api/feeds//activity": { + "endpoint": "api_feed_activity", + "methods": [ + "GET" + ] + }, + "/api/feeds//errors": { + "endpoint": "api_feed_errors", + "methods": [ + "GET" + ] + }, + "/api/feeds//errors/reset": { + "POST locked anon": { + "content_type": "application/json", + "csp_script_src": "script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "", + "status": 401, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "endpoint": "api_reset_feed_errors", + "methods": [ + "POST" + ] + }, + "/api/feeds//refresh": { + "POST locked anon": { + "content_type": "application/json", + "csp_script_src": "script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "", + "status": 401, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "endpoint": "api_refresh_feed", + "methods": [ + "POST" + ] + }, + "/api/feeds/default-format": { + "GET locked anon": { + "content_type": "application/json", + "csp_script_src": "script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "", + "status": 401, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "GET open": { + "content_type": "application/json", + "csp_script_src": "script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "", + "status": 200, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "endpoint": "api_get_default_format", + "methods": [ + "GET" + ] + }, + "/api/feeds/errors/reset": { + "POST locked anon": { + "content_type": "application/json", + "csp_script_src": "script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "", + "status": 401, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "endpoint": "api_reset_all_feed_errors", + "methods": [ + "POST" + ] + }, + "/api/feeds/preview": { + "POST locked anon": { + "content_type": "application/json", + "csp_script_src": "script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "", + "status": 401, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "endpoint": "api_preview_feed", + "methods": [ + "POST" + ] + }, + "/api/feeds/stats": { + "GET locked anon": { + "content_type": "application/json", + "csp_script_src": "script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "", + "status": 401, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "GET open": { + "content_type": "application/json", + "csp_script_src": "script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "", + "status": 200, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "endpoint": "api_feed_stats", + "methods": [ + "GET" + ] + }, + "/api/feeds/test": { + "POST locked anon": { + "content_type": "application/json", + "csp_script_src": "script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "", + "status": 401, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "endpoint": "api_test_feed", + "methods": [ + "POST" + ] + }, + "/api/geocode-contact": { + "POST locked anon": { + "content_type": "application/json", + "csp_script_src": "script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "", + "status": 401, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "endpoint": "api_geocode_contact", + "methods": [ + "POST" + ] + }, + "/api/greeter": { + "GET locked anon": { + "content_type": "application/json", + "csp_script_src": "script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "", + "status": 401, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "GET open": { + "content_type": "application/json", + "csp_script_src": "script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "", + "status": 200, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "endpoint": "api_greeter", + "methods": [ + "GET" + ] + }, + "/api/greeter/end-rollout": { + "POST locked anon": { + "content_type": "application/json", + "csp_script_src": "script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "", + "status": 401, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "endpoint": "api_end_rollout", + "methods": [ + "POST" + ] + }, + "/api/greeter/ungreet": { + "POST locked anon": { + "content_type": "application/json", + "csp_script_src": "script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "", + "status": 401, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "endpoint": "api_ungreet_user", + "methods": [ + "POST" + ] + }, + "/api/health": { + "GET locked anon": { + "content_type": "application/json", + "csp_script_src": "script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "", + "status": 200, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "GET open": { + "content_type": "application/json", + "csp_script_src": "script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "", + "status": 200, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "endpoint": "api_health", + "methods": [ + "GET" + ] + }, + "/api/maintenance/backup_now": { + "POST locked anon": { + "content_type": "application/json", + "csp_script_src": "script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "", + "status": 401, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "endpoint": "api_maintenance_backup_now", + "methods": [ + "POST" + ] + }, + "/api/maintenance/list_backups": { + "GET locked anon": { + "content_type": "application/json", + "csp_script_src": "script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "", + "status": 401, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "GET open": { + "content_type": "application/json", + "csp_script_src": "script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "", + "status": 200, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "endpoint": "api_maintenance_list_backups", + "methods": [ + "GET" + ] + }, + "/api/maintenance/purge": { + "POST locked anon": { + "content_type": "application/json", + "csp_script_src": "script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "", + "status": 401, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "endpoint": "api_maintenance_purge", + "methods": [ + "POST" + ] + }, + "/api/maintenance/restore": { + "POST locked anon": { + "content_type": "application/json", + "csp_script_src": "script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "", + "status": 401, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "endpoint": "api_maintenance_restore", + "methods": [ + "POST" + ] + }, + "/api/maintenance/status": { + "GET locked anon": { + "content_type": "application/json", + "csp_script_src": "script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "", + "status": 401, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "GET open": { + "content_type": "application/json", + "csp_script_src": "script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "", + "status": 200, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "endpoint": "api_maintenance_status", + "methods": [ + "GET" + ] + }, + "/api/mesh/edges": { + "GET locked anon": { + "content_type": "application/json", + "csp_script_src": "script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "", + "status": 200, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "GET open": { + "content_type": "application/json", + "csp_script_src": "script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "", + "status": 200, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "endpoint": "api_mesh_edges", + "methods": [ + "GET" + ] + }, + "/api/mesh/nodes": { + "GET locked anon": { + "content_type": "application/json", + "csp_script_src": "script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "", + "status": 200, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "GET open": { + "content_type": "application/json", + "csp_script_src": "script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "", + "status": 200, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "endpoint": "api_mesh_nodes", + "methods": [ + "GET" + ] + }, + "/api/mesh/resolve-path": { + "POST locked anon": { + "content_type": "application/json", + "csp_script_src": "script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "", + "status": 400, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "endpoint": "api_resolve_path", + "methods": [ + "POST" + ] + }, + "/api/mesh/stats": { + "GET locked anon": { + "content_type": "application/json", + "csp_script_src": "script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "", + "status": 200, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "GET open": { + "content_type": "application/json", + "csp_script_src": "script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "", + "status": 200, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "endpoint": "api_mesh_stats", + "methods": [ + "GET" + ] + }, + "/api/multibyte-rollout": { + "GET locked anon": { + "content_type": "application/json", + "csp_script_src": "script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "", + "status": 401, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "GET open": { + "content_type": "text/html", + "csp_script_src": "script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "", + "status": 404, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "endpoint": "api_multibyte_rollout", + "methods": [ + "GET" + ] + }, + "/api/optimize-database": { + "POST locked anon": { + "content_type": "application/json", + "csp_script_src": "script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "", + "status": 401, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "endpoint": "api_optimize_database", + "methods": [ + "POST" + ] + }, + "/api/plugins": { + "GET locked anon": { + "content_type": "application/json", + "csp_script_src": "script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "", + "status": 401, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "GET open": { + "content_type": "application/json", + "csp_script_src": "script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "", + "status": 200, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "endpoint": "api_plugins_get", + "methods": [ + "GET" + ] + }, + "/api/plugins//": { + "POST locked anon": { + "content_type": "application/json", + "csp_script_src": "script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "", + "status": 401, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "endpoint": "api_plugins_save", + "methods": [ + "POST" + ] + }, + "/api/plugins/reload-status": { + "GET locked anon": { + "content_type": "application/json", + "csp_script_src": "script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "", + "status": 401, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "GET open": { + "content_type": "application/json", + "csp_script_src": "script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "", + "status": 200, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "endpoint": "api_plugins_reload_status", + "methods": [ + "GET" + ] + }, + "/api/radio/advert": { + "POST locked anon": { + "content_type": "application/json", + "csp_script_src": "script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "", + "status": 401, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "endpoint": "api_radio_advert", + "methods": [ + "POST" + ] + }, + "/api/radio/connect": { + "POST locked anon": { + "content_type": "application/json", + "csp_script_src": "script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "", + "status": 401, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "endpoint": "api_radio_connect", + "methods": [ + "POST" + ] + }, + "/api/radio/firmware/config/read": { + "POST locked anon": { + "content_type": "application/json", + "csp_script_src": "script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "", + "status": 401, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "endpoint": "api_firmware_config_read", + "methods": [ + "POST" + ] + }, + "/api/radio/firmware/config/write": { + "POST locked anon": { + "content_type": "application/json", + "csp_script_src": "script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "", + "status": 401, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "endpoint": "api_firmware_config_write", + "methods": [ + "POST" + ] + }, + "/api/radio/params": { + "POST locked anon": { + "content_type": "application/json", + "csp_script_src": "script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "", + "status": 401, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "endpoint": "api_radio_params_write", + "methods": [ + "POST" + ] + }, + "/api/radio/reboot": { + "POST locked anon": { + "content_type": "application/json", + "csp_script_src": "script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "", + "status": 401, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "endpoint": "api_radio_reboot", + "methods": [ + "POST" + ] + }, + "/api/radio/status": { + "GET locked anon": { + "content_type": "application/json", + "csp_script_src": "script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "", + "status": 401, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "GET open": { + "content_type": "application/json", + "csp_script_src": "script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "", + "status": 200, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "endpoint": "api_radio_status", + "methods": [ + "GET" + ] + }, + "/api/recent_commands": { + "GET locked anon": { + "content_type": "application/json", + "csp_script_src": "script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "", + "status": 401, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "GET open": { + "content_type": "application/json", + "csp_script_src": "script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "", + "status": 200, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "endpoint": "api_recent_commands", + "methods": [ + "GET" + ] + }, + "/api/region-scopes": { + "POST locked anon": { + "content_type": "application/json", + "csp_script_src": "script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "", + "status": 401, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "endpoint": "api_region_scopes_save", + "methods": [ + "POST" + ] + }, + "/api/region-warnings": { + "GET locked anon": { + "content_type": "application/json", + "csp_script_src": "script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "", + "status": 401, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "GET open": { + "content_type": "application/json", + "csp_script_src": "script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "", + "status": 200, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "endpoint": "api_region_warnings", + "methods": [ + "GET" + ] + }, + "/api/region-warnings/settings": { + "POST locked anon": { + "content_type": "application/json", + "csp_script_src": "script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "", + "status": 401, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "endpoint": "api_region_warnings_save", + "methods": [ + "POST" + ] + }, + "/api/scheduled-messages": { + "PUT locked anon": { + "content_type": "application/json", + "csp_script_src": "script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "", + "status": 401, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "endpoint": "api_update_scheduled_message", + "methods": [ + "PUT" + ] + }, + "/api/scheduled-messages/preview": { + "POST locked anon": { + "content_type": "application/json", + "csp_script_src": "script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "", + "status": 401, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "endpoint": "api_scheduled_messages_preview", + "methods": [ + "POST" + ] + }, + "/api/stats": { + "GET locked anon": { + "content_type": "application/json", + "csp_script_src": "script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "", + "status": 200, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "GET open": { + "content_type": "application/json", + "csp_script_src": "script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "", + "status": 200, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "endpoint": "api_stats", + "methods": [ + "GET" + ] + }, + "/api/stats/rate_limiters": { + "GET locked anon": { + "content_type": "application/json", + "csp_script_src": "script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "", + "status": 401, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "GET open": { + "content_type": "application/json", + "csp_script_src": "script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "", + "status": 200, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "endpoint": "api_rate_limiter_stats", + "methods": [ + "GET" + ] + }, + "/api/stream_data": { + "POST locked anon": { + "content_type": "application/json", + "csp_script_src": "script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "", + "status": 400, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "endpoint": "api_stream_data", + "methods": [ + "POST" + ] + }, + "/api/system-health": { + "GET locked anon": { + "content_type": "application/json", + "csp_script_src": "script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "", + "status": 401, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "GET open": { + "content_type": "application/json", + "csp_script_src": "script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "", + "status": 200, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "endpoint": "api_system_health", + "methods": [ + "GET" + ] + }, + "/api/toggle-star-contact": { + "POST locked anon": { + "content_type": "application/json", + "csp_script_src": "script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "", + "status": 401, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "endpoint": "api_toggle_star_contact", + "methods": [ + "POST" + ] + }, + "/apple-touch-icon.png": { + "GET locked anon": { + "content_type": "image/png", + "csp_script_src": "script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "", + "status": 200, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "GET open": { + "content_type": "image/png", + "csp_script_src": "script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "", + "status": 200, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "endpoint": "apple_touch_icon", + "methods": [ + "GET" + ] + }, + "/cache": { + "GET locked anon": { + "content_type": "text/html", + "csp_script_src": "script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "/login?next=/cache", + "status": 302, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "GET open": { + "content_type": "text/html", + "csp_script_src": "script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "/config#database", + "status": 302, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "endpoint": "cache", + "methods": [ + "GET" + ] + }, + "/config": { + "GET locked anon": { + "content_type": "text/html", + "csp_script_src": "script-src 'self' 'nonce-X' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "/login?next=/config", + "status": 302, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "GET open": { + "content_type": "text/html", + "csp_script_src": "script-src 'self' 'nonce-X' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "", + "status": 200, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "endpoint": "config_page", + "methods": [ + "GET" + ] + }, + "/contacts": { + "GET locked anon": { + "content_type": "text/html", + "csp_script_src": "script-src 'self' 'nonce-X' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "", + "status": 200, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "GET open": { + "content_type": "text/html", + "csp_script_src": "script-src 'self' 'nonce-X' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "", + "status": 200, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "endpoint": "contacts", + "methods": [ + "GET" + ] + }, + "/favicon-16x16.png": { + "GET locked anon": { + "content_type": "image/png", + "csp_script_src": "script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "", + "status": 200, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "GET open": { + "content_type": "image/png", + "csp_script_src": "script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "", + "status": 200, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "endpoint": "favicon_16x16", + "methods": [ + "GET" + ] + }, + "/favicon-32x32.png": { + "GET locked anon": { + "content_type": "image/png", + "csp_script_src": "script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "", + "status": 200, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "GET open": { + "content_type": "image/png", + "csp_script_src": "script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "", + "status": 200, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "endpoint": "favicon_32x32", + "methods": [ + "GET" + ] + }, + "/favicon.ico": { + "GET locked anon": { + "content_type": "image/x-icon", + "csp_script_src": "script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "", + "status": 200, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "GET open": { + "content_type": "image/x-icon", + "csp_script_src": "script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "", + "status": 200, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "endpoint": "favicon", + "methods": [ + "GET" + ] + }, + "/feeds": { + "GET locked anon": { + "content_type": "text/html", + "csp_script_src": "script-src 'self' 'nonce-X' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "/login?next=/feeds", + "status": 302, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "GET open": { + "content_type": "text/html", + "csp_script_src": "script-src 'self' 'nonce-X' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "", + "status": 200, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "endpoint": "feeds", + "methods": [ + "GET" + ] + }, + "/greeter": { + "GET locked anon": { + "content_type": "text/html", + "csp_script_src": "script-src 'self' 'nonce-X' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "/login?next=/greeter", + "status": 302, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "GET open": { + "content_type": "text/html", + "csp_script_src": "script-src 'self' 'nonce-X' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "", + "status": 200, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "endpoint": "greeter", + "methods": [ + "GET" + ] + }, + "/login": { + "GET locked anon": { + "content_type": "text/html", + "csp_script_src": "script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "", + "status": 200, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "GET open": { + "content_type": "text/html", + "csp_script_src": "script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "/", + "status": 302, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "POST locked anon": { + "content_type": "text/html", + "csp_script_src": "script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "", + "status": 200, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "endpoint": "login", + "methods": [ + "GET", + "POST" + ] + }, + "/logout": { + "GET locked anon": { + "content_type": "text/html", + "csp_script_src": "script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "/", + "status": 302, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "GET open": { + "content_type": "text/html", + "csp_script_src": "script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "/", + "status": 302, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "endpoint": "logout", + "methods": [ + "GET" + ] + }, + "/logs": { + "GET locked anon": { + "content_type": "text/html", + "csp_script_src": "script-src 'self' 'nonce-X' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "/login?next=/logs", + "status": 302, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "GET open": { + "content_type": "text/html", + "csp_script_src": "script-src 'self' 'nonce-X' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "", + "status": 200, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "endpoint": "logs", + "methods": [ + "GET" + ] + }, + "/mesh": { + "GET locked anon": { + "content_type": "text/html", + "csp_script_src": "script-src 'self' 'nonce-X' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "", + "status": 200, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "GET open": { + "content_type": "text/html", + "csp_script_src": "script-src 'self' 'nonce-X' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "", + "status": 200, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "endpoint": "mesh", + "methods": [ + "GET" + ] + }, + "/multibyte-rollout": { + "GET locked anon": { + "content_type": "text/html", + "csp_script_src": "script-src 'self' 'nonce-X' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "/login?next=/multibyte-rollout", + "status": 302, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "GET open": { + "content_type": "text/html", + "csp_script_src": "script-src 'self' 'nonce-X' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "", + "status": 404, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "endpoint": "multibyte_rollout", + "methods": [ + "GET" + ] + }, + "/plugins": { + "GET locked anon": { + "content_type": "text/html", + "csp_script_src": "script-src 'self' 'nonce-X' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "/login?next=/plugins", + "status": 302, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "GET open": { + "content_type": "text/html", + "csp_script_src": "script-src 'self' 'nonce-X' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "", + "status": 200, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "endpoint": "plugins_page", + "methods": [ + "GET" + ] + }, + "/radio": { + "GET locked anon": { + "content_type": "text/html", + "csp_script_src": "script-src 'self' 'nonce-X' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "/login?next=/radio", + "status": 302, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "GET open": { + "content_type": "text/html", + "csp_script_src": "script-src 'self' 'nonce-X' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "", + "status": 200, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "endpoint": "radio", + "methods": [ + "GET" + ] + }, + "/realtime": { + "GET locked anon": { + "content_type": "text/html", + "csp_script_src": "script-src 'self' 'nonce-X' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "", + "status": 200, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "GET open": { + "content_type": "text/html", + "csp_script_src": "script-src 'self' 'nonce-X' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "", + "status": 200, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "endpoint": "realtime", + "methods": [ + "GET" + ] + }, + "/region-warnings": { + "GET locked anon": { + "content_type": "text/html", + "csp_script_src": "script-src 'self' 'nonce-X' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "/login?next=/region-warnings", + "status": 302, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "GET open": { + "content_type": "text/html", + "csp_script_src": "script-src 'self' 'nonce-X' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "", + "status": 200, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "endpoint": "region_warnings_page", + "methods": [ + "GET" + ] + }, + "/schedule": { + "GET locked anon": { + "content_type": "text/html", + "csp_script_src": "script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "/login?next=/schedule", + "status": 302, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "GET open": { + "content_type": "text/html", + "csp_script_src": "script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "", + "status": 200, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "endpoint": "schedule_page", + "methods": [ + "GET" + ] + }, + "/site.webmanifest": { + "GET locked anon": { + "content_type": "application/manifest+json", + "csp_script_src": "script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "", + "status": 200, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "GET open": { + "content_type": "application/manifest+json", + "csp_script_src": "script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com", + "location": "", + "status": 200, + "x_content_type_options": "nosniff", + "x_frame_options": "SAMEORIGIN" + }, + "endpoint": "site_webmanifest", + "methods": [ + "GET" + ] + } +} diff --git a/tests/characterization/golden_util.py b/tests/characterization/golden_util.py index 63326ea..8ad61f5 100644 --- a/tests/characterization/golden_util.py +++ b/tests/characterization/golden_util.py @@ -53,7 +53,7 @@ def _diff(exp: Any, got: Any, path: str = "") -> list[str]: if len(exp) != len(got): return [f"{path}: length {len(exp)} -> {len(got)}"] out = [] - for i, (a, b) in enumerate(zip(exp, got)): + for i, (a, b) in enumerate(zip(exp, got, strict=True)): out.extend(_diff(a, b, f"{path}[{i}]")) return out return [] if exp == got else [f"{path}: {exp!r} -> {got!r}"] diff --git a/tests/characterization/test_bot_identity_matrix.py b/tests/characterization/test_bot_identity_matrix.py index 72aefd8..0ee3737 100644 --- a/tests/characterization/test_bot_identity_matrix.py +++ b/tests/characterization/test_bot_identity_matrix.py @@ -35,7 +35,7 @@ class _Cmd(BaseCommand): def _shapes(): yield "self_info=None", None for combo in itertools.product(VALUES, repeat=len(FIELDS)): - present = {f: (f"{v}-{f}" if v == "Dev" else v) for f, v in zip(FIELDS, combo) if v is not None} + present = {f: (f"{v}-{f}" if v == "Dev" else v) for f, v in zip(FIELDS, combo, strict=True) if v is not None} label = ",".join(f"{k}={v!r}" for k, v in present.items()) or "empty" yield f"dict[{label}]", dict(present) yield f"obj[{label}]", SimpleNamespace(**present) diff --git a/tests/characterization/test_packaging_coverage.py b/tests/characterization/test_packaging_coverage.py new file mode 100644 index 0000000..f229968 --- /dev/null +++ b/tests/characterization/test_packaging_coverage.py @@ -0,0 +1,69 @@ +"""Every tracked runtime file must ship in the wheel. + +``pyproject.toml`` uses ``include-package-data = false`` and lists package data +explicitly, and package discovery is the regular (non-namespace) kind, so a new +subpackage without ``__init__.py`` or a new data file outside the globs is +silently left out of an installed bot. This checks the configuration against +``git ls-files`` without building a wheel (the dev venv has no setuptools). +""" + +from __future__ import annotations + +import fnmatch +import subprocess +from pathlib import Path + +import pytest + +tomllib = pytest.importorskip("tomllib") # Python 3.11+; CI's 3.11-3.13 jobs cover it + +ROOT = Path(__file__).resolve().parents[2] + + +def _tracked(prefix: str) -> list[str]: + try: + out = subprocess.run( + ["git", "ls-files", prefix], cwd=ROOT, capture_output=True, text=True, check=True + ).stdout + except (OSError, subprocess.CalledProcessError): + pytest.skip("not a git checkout") + return [line for line in out.splitlines() if line] + + +def _config(): + return tomllib.loads((ROOT / "pyproject.toml").read_text(encoding="utf-8"))["tool"]["setuptools"] + + +def test_every_python_package_directory_is_a_regular_package(): + missing = sorted( + { + str(Path(f).parent) + for f in _tracked("modules") + if f.endswith(".py") and not (ROOT / Path(f).parent / "__init__.py").exists() + } + ) + assert missing == [], f"directories with modules but no __init__.py (not packaged): {missing}" + + +def test_every_non_python_runtime_file_matches_a_package_data_glob(): + package_data: dict[str, list[str]] = _config()["package-data"] + uncovered = [] + for prefix, package in (("modules", "modules"), ("translations", "translations"), ("data/randomlines", "data.randomlines")): + globs = package_data.get(package, []) + base = ROOT / package.replace(".", "/") + for f in _tracked(prefix): + if f.endswith((".py", ".pyc", ".md")) or "/__pycache__/" in f: + continue + rel = (ROOT / f).relative_to(base).as_posix() + # setuptools globs: '*' does not cross '/', '**' does. + if not any(_glob_match(rel, g) for g in globs): + uncovered.append(f) + assert uncovered == [], f"runtime files not covered by [tool.setuptools.package-data]: {uncovered}" + + +def _glob_match(rel: str, pattern: str) -> bool: + if "**" in pattern: + head, _, tail = pattern.partition("**/") + return rel.startswith(head) and fnmatch.fnmatch(rel[len(head):].split("/")[-1], tail) if tail else rel.startswith(head) + rel_parts, pat_parts = rel.split("/"), pattern.split("/") + return len(rel_parts) == len(pat_parts) and all(fnmatch.fnmatch(r, p) for r, p in zip(rel_parts, pat_parts, strict=True)) diff --git a/tests/characterization/test_web_endpoint_inventory.py b/tests/characterization/test_web_endpoint_inventory.py new file mode 100644 index 0000000..3a74567 --- /dev/null +++ b/tests/characterization/test_web_endpoint_inventory.py @@ -0,0 +1,94 @@ +"""Characterization: the web viewer's route table and how each route answers. + +Records every rule (path, methods, endpoint name) and, for parameterless GET +routes, the status, content type and security-relevant headers of a request +made anonymously with and without a password configured. It also records, for +every mutating route, what an anonymous request gets when a password is set. +Helper extraction or route regrouping in ``app.py`` must leave this unchanged: +endpoint names feed the CSP nonce allowlist and ``url_for``, and auth is +path-based. +""" + +from __future__ import annotations + +import configparser +import re +from unittest.mock import patch + +import pytest + +from tests.characterization.golden_util import assert_golden + +SKIP_GET = { + # Streams or long-polls; not a single request/response. + "/socket.io/", +} + + +def _viewer(tmp_path, password: str): + from modules.web_viewer.app import BotDataViewer + + tmp_path.mkdir(parents=True, exist_ok=True) + config = configparser.ConfigParser() + config.add_section("Bot") + config.set("Bot", "db_path", str(tmp_path / "meshcore_bot.db")) + config.set("Bot", "bot_name", "InvBot") + config.add_section("Web_Viewer") + for key, value in [ + ("host", "127.0.0.1"), ("port", "8080"), ("enabled", "false"), + ("auto_start", "false"), ("debug", "false"), + ("cors_allowed_origins", "*"), ("web_viewer_password", password), + ]: + config.set("Web_Viewer", key, value) + config_path = str(tmp_path / "config.ini") + with open(config_path, "w") as handle: + config.write(handle) + with patch.object(BotDataViewer, "_start_database_polling"), \ + patch.object(BotDataViewer, "_start_log_tailing"), \ + patch.object(BotDataViewer, "_start_cleanup_scheduler"), \ + patch.object(BotDataViewer, "_start_dashboard_refresher"), \ + patch.object(BotDataViewer, "_setup_socketio_handlers"), \ + patch("modules.web_viewer.app.RepeaterManager"): + viewer = BotDataViewer(db_path=str(tmp_path / "meshcore_bot.db"), config_path=config_path) + viewer.app.testing = True + return viewer + + +def _headers(resp): + csp = resp.headers.get("Content-Security-Policy", "") + script_src = next((p.strip() for p in csp.split(";") if p.strip().startswith("script-src")), "") + return { + "status": resp.status_code, + "content_type": (resp.headers.get("Content-Type") or "").split(";")[0], + "location": re.sub(r"https?://[^/]+", "", resp.headers.get("Location", "")), + "csp_script_src": re.sub(r"'nonce-[^']+'", "'nonce-X'", script_src), + "x_frame_options": resp.headers.get("X-Frame-Options"), + "x_content_type_options": resp.headers.get("X-Content-Type-Options"), + } + + +@pytest.mark.timeout(120) +def test_route_table_and_anonymous_responses(tmp_path): + open_viewer = _viewer(tmp_path / "open", "") + locked_viewer = _viewer(tmp_path / "locked", "s3cret-pass") + rules = sorted(open_viewer.app.url_map.iter_rules(), key=lambda r: (r.rule, r.endpoint)) + inventory = {} + open_client = open_viewer.app.test_client() + locked_client = locked_viewer.app.test_client() + for rule in rules: + if rule.endpoint == "static": + continue + methods = sorted(m for m in rule.methods if m not in ("HEAD", "OPTIONS")) + entry = {"endpoint": rule.endpoint, "methods": methods} + has_params = bool(rule.arguments) + if "GET" in methods and not has_params and rule.rule not in SKIP_GET: + entry["GET open"] = _headers(open_client.get(rule.rule)) + entry["GET locked anon"] = _headers(locked_client.get(rule.rule)) + for method in methods: + if method == "GET": + continue + path = re.sub(r"<(?:[^:>]+:)?[^>]+>", "1", rule.rule) + resp = locked_client.open(path, method=method, json={}) + entry[f"{method} locked anon"] = _headers(resp) + inventory[f"{rule.rule}"] = entry + assert_golden("web_endpoint_inventory", inventory)