104 lines
3.4 KiB
Markdown
104 lines
3.4 KiB
Markdown
# 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.
|