# Hotfix Patches — v0.18.1 → v0.18.2 Priority order: apply top-to-bottom. All patches are independent except 1 and 2 which must be applied together. --- ## Patch 1+2: Harden JSON scanning + SafeJSON (P0 — fixes broken chat list) **Root cause:** Corrupt `\x02` byte in a channel's `settings` column. `scanJSON` accepts it, Gin's `c.JSON()` starts writing 200 + headers, `json.RawMessage.MarshalJSON()` hits the bad byte, aborts, browser gets truncated body → "Unexpected end of JSON input" → 0 chats loaded. **Files:** 1. **NEW:** `server/handlers/safe_json.go` Copy `patches/server/handlers/safe_json.go` into your tree. Contains: `SafeJSON()`, hardened `scanJSON()`, hardened `scanTags()`. 2. **NEW:** `server/handlers/safe_json_test.go` Copy `patches/server/handlers/safe_json_test.go` into your tree. 3. **EDIT:** `server/handlers/channels.go` Apply these three changes (or diff against `patches/server/handlers/channels.go`): a. Remove the old `scanJSON` + `jsonScanner` + `scanTags` + `tagsScanner` definitions (~lines 101–155). Replace with a comment: ```go // scanJSON, scanTags, SafeJSON → safe_json.go ``` b. Replace `c.JSON(http.StatusOK, paginatedResponse{` in ListChannels with `SafeJSON(c, http.StatusOK, paginatedResponse{` c. Replace `c.JSON(http.StatusCreated, ch)` at end of CreateChannel with `SafeJSON(c, http.StatusCreated, ch)` d. Replace `c.JSON(http.StatusOK, ch)` at end of GetChannel with `SafeJSON(c, http.StatusOK, ch)` **Verify:** `go build ./...` and `go test ./server/handlers/ -run TestScanJSON -v` **Data fix:** Find and repair the corrupt row: ```sql -- Find it SELECT id, title, length(settings::text), encode(substring(settings::bytea, 1, 20), 'hex') AS hex_preview FROM channels WHERE settings IS NOT NULL AND settings::text ~ '[^\x20-\x7E\t\n\r]'; -- Fix it (reset to empty settings) UPDATE channels SET settings = '{}' WHERE settings IS NOT NULL AND settings::text ~ '[^\x20-\x7E\t\n\r]'; ``` --- ## Patch 3: Service Worker scheme guard (P1 — console noise) **Root cause:** SW fetch handler tries to cache `chrome-extension://` URLs. `Cache.put()` rejects non-http(s) schemes. **File:** `src/sw.js` Add scheme guard after `const url = new URL(event.request.url);`: ```js // Only handle http/https — ignore chrome-extension://, moz-extension://, etc. if (url.protocol !== 'https:' && url.protocol !== 'http:') { return; } ``` Or copy `patches/src/sw.js`. --- ## Patch 4: Settings debounce (P2 — cosmetic waste) Not included as a patch file — straightforward to implement: - In `app.js` init sequence, debounce `API.updateSettings()` calls - A 100ms `setTimeout` wrapper that coalesces multiple rapid saves --- ## Future work (not in this hotfix) - Apply `SafeJSON` to extension list endpoints (lines 47, 115 of extensions.go) — same vulnerability pattern, lower risk since extension data is seeded from known-good JSON. - Harden `JSONMap.Scan` in `models/models.go` to warn-and-default instead of returning error. Currently it fails the whole query which gives a clean 500, but it means one corrupt row blocks all results in a list query. - Startup JSON integrity check (design doc §17.1 Layer 4) — scan all JSONB columns on boot, log warnings for corrupt data. Not blocking for hotfix but valuable for post-upgrade diagnostics. - JWT redaction in Gin log formatter for WebSocket URLs.