| Author | Andy Green <andy@warmcat.com> 2026-09-06 06:35 UTC | | Committer | Andy Green <andy@warmcat.com> 2026-09-06 06:35 UTC | | Tree | 6645d696c75b37225440d8d09d6dfbce795c4bb1 Raw Patch | | | web: drop browser-originated watcher_services, harden server web link rx, fixes F-002 | web: drop browser-originated watcher_services, harden server web link rx, fixes F-002
An unauthenticated browser could send
{"schema":"com.warmcat.sai.watcher_services"} on /sai/browse*: the
schema is in the browser rx map and its switch case fell into the
generic forward to sai-server over the nailed-up websrv ss link. But
sai-server's web link schema map has no entry for it, so the decode
failed and websrvss_ws_rx() returned LWSSSSRET_DISCONNECT_ME, tearing
down the ss link; the retry policy reconnects after 1/2/3s backoff so
a repeat sender keeps the control plane flapping, and admin operations
forwarded on it fail while it is down.
Watcher services are a server config-file concern
(sais_config_watchers()); the schema only ever flows web -> browsers.
Give it its own case that logs and drops it, matching the loadreport
treatment from F-001.
As a second layer, sai-server no longer treats an undecodable message
on the web link as fatal: the link carries forwarded browser requests
whose content we do not control, so a message that fails to decode is
logged, its partial lwsac freed, and skipped, reserving disconnect for
protocol-level unrecoverable states. This also fixes a small lwsac
leak on the decode-failure path.
|
diff --git a/src/server/s-ws-web.c b/src/server/s-ws-web.c
index 0a22b2e..7b3600f 100644
--- a/src/server/s-ws-web.c
+++ b/src/server/s-ws-web.c
@@ -467,10 +467,19 @@ websrvss_ws_rx(void *userobj, const uint8_t *buf, size_t len, int flags)
lws_struct_json_init_parse(&m->ctx, NULL, &a);
n = lejp_parse(&m->ctx, (uint8_t *)buf, (int)len);
if (n < 0 || !a.dest) {
+ /*
+ * This link carries forwarded browser requests whose content
+ * we do not control, so an undecodable message is skipped,
+ * not fatal: tearing down the ss link here would take the
+ * nailed-up control channel away from every connected
+ * sai-web instance for the retry period.
+ */
lwsl_hexdump_notice(buf, len);
lwsl_notice("%s: notification JSON decode failed '%s'\n",
__func__, lejp_error_to_string(n));
- return LWSSSSRET_DISCONNECT_ME;
+ lwsac_free(&a.ac);
+
+ return 0;
}
// lwsl_notice("%s: schema idx %d\n", __func__, a.top_schema_index);
diff --git a/src/web/w-ws-browser.c b/src/web/w-ws-browser.c
index 3ac9dc2..7c57ae5 100644
--- a/src/web/w-ws-browser.c
+++ b/src/web/w-ws-browser.c
@@ -1090,7 +1090,6 @@ saiw_ws_json_rx_browser(struct vhd *vhd, struct pss *pss, uint8_t *buf,
*/
break;
- case SAIM_WS_BROWSER_RX_WATCHER_SERVICES:
case SAIM_WS_BROWSER_RX_OPENSHELL:
case SAIM_WS_BROWSER_RX_CLOSESHELL:
case SAIM_WS_BROWSER_RX_PTYDATA:
@@ -1106,6 +1105,17 @@ saiw_ws_json_rx_browser(struct vhd *vhd, struct pss *pss, uint8_t *buf,
lwsl_notice("%s: dropping loadreport from browser\n", __func__);
goto ok;
+ /*
+ * Watcher services are a server config-file concern; the schema only
+ * ever flows from us towards browsers. A browser sending one is
+ * meaningless: drop it locally rather than forward it, sai-server
+ * does not accept this schema on the web link.
+ */
+ case SAIM_WS_BROWSER_RX_WATCHER_SERVICES:
+ lwsl_notice("%s: dropping watcher_services from browser\n",
+ __func__);
+ goto ok;
+
default:
/*
* No schema in the map today reaches here. If one is added
|