Bug fixing sweep: end-to-end audit of all features #33

Closed
opened 2026-09-09 01:50:46 +00:00 by poslop · 1 comment
Owner

Systematic pass over every feature and page trying to break it through normal use: create/view/delete flows, expiry edge cases (1h, past dates, never), burn-after-read double reads, custom slug validation edges (unicode, very long, reserved, collisions), password flows, pagination boundaries (empty pages, offset beyond total), filter states, back/forward navigation, concurrent requests. File findings as separate issues; fix in this sweep.

Systematic pass over every feature and page trying to break it through normal use: create/view/delete flows, expiry edge cases (1h, past dates, never), burn-after-read double reads, custom slug validation edges (unicode, very long, reserved, collisions), password flows, pagination boundaries (empty pages, offset beyond total), filter states, back/forward navigation, concurrent requests. File findings as separate issues; fix in this sweep.
Author
Owner

Bug-fixing sweep complete. Full systematic pass over create/view/burn/expiry/history/saved/navigation/concurrency flows against the live server, plus code review of main.go/web.go/burn.go/cans.go/customslug.go.

Bugs found & fixed (03600b2)

  1. view_count never incremented on HTML paste views — handlePasteView (web.go) was missing IncrementViews; only /raw counted views, so the counter stayed at 0 no matter how many times a paste was opened. Fixed + regression test (TestPasteViewIncrementsViewCount). The just-created banner render (?created=1) correctly does NOT count (TestJustCreatedViewDoesNotCount).
  2. API accepted invalid expiry durations — the UI enforces 1min–1yr (#48) but the API accepted -1h, 0s, 1ns (born-expired pastes) and 30000h (~3.4 years). Fixed with validExpiry bounds in CreatePaste + table test (TestCreatePasteExpiryBounds). Verified live: 400 for all invalid, 201 for 1m/90s/8760h.
  3. History/saved search filter ignored custom_slug — table.js matches() only checked title+id, yet the URL column shows the slug. Slug terms now match.

Verified working (no fix needed)

  • Custom slugs: reserved words rejected (incl. case-insensitive API), leading dash/unicode/space/65-char rejected, 64-char ok, collision 400; live concurrency test: 8 parallel same-slug creates → exactly one 201, rest rejected, slug serves winner.
  • Burn-after-N: budget correct for cookie viewers (dedupe window), distinct viewers each consume one read, no-cookie clients count as their own viewer and burn correctly; burned paste → 404 on view/raw/API. Password+burn works via unlock form and API password. Expiry+burn both displayed.
  • Expired pastes 404 on view/raw/API. Unlisted: not in /api/public, viewable, in creator's /api/mine. Soft-deleted paste disappears from /api/mine immediately; offset beyond total → empty list not error; limit=0/-5/10⁶/abc all clamp safely.
  • XSS-safety of titles through create→history→view pipeline (quotes, angle brackets, emoji) — all properly escaped; title round-trips through API unchanged.
  • Highlighted vs unknown languages, blank lines, trailing newline, 5000-char single line, CRLF content, emoji — gutter counts and rendering correct. 5MB paste: create 1.1s, raw 0.4s, HTML view instant.
  • Unlock flow: wrong password → rate-limited after 5 tries (429 includes correct-password attempts, acceptable), valid unlock sets per-paste HMAC cookie, subsequent GETs unlocked. Direct /settings & /mine without cookie render fine.

Quirks noted (not fixed, by design or low value)

  • /raw on a password-protected paste returns 401 with no unlock path (raw is meant for password-less pastes).
  • Burned/expired paste IDs are never reused; deletion token hard-delete works and also frees nothing (ids unique random).
  • GET /{id}/ with trailing slash → 404 (chi default).
  • Duplicate parallel DELETEs of the same paste all return 200 (idempotent soft-delete) — harmless.

Test pastes cleaned up (72 removed, DB back to pre-sweep seeded state). go test ./... passes; service rebuilt, restarted and verified live.

Bug-fixing sweep complete. Full systematic pass over create/view/burn/expiry/history/saved/navigation/concurrency flows against the live server, plus code review of main.go/web.go/burn.go/cans.go/customslug.go. ## Bugs found & fixed (03600b2) 1. **view_count never incremented on HTML paste views** — handlePasteView (web.go) was missing IncrementViews; only /raw counted views, so the counter stayed at 0 no matter how many times a paste was opened. Fixed + regression test (TestPasteViewIncrementsViewCount). The just-created banner render (?created=1) correctly does NOT count (TestJustCreatedViewDoesNotCount). 2. **API accepted invalid expiry durations** — the UI enforces 1min–1yr (#48) but the API accepted `-1h`, `0s`, `1ns` (born-expired pastes) and `30000h` (~3.4 years). Fixed with validExpiry bounds in CreatePaste + table test (TestCreatePasteExpiryBounds). Verified live: 400 for all invalid, 201 for 1m/90s/8760h. 3. **History/saved search filter ignored custom_slug** — table.js `matches()` only checked title+id, yet the URL column shows the slug. Slug terms now match. ## Verified working (no fix needed) - Custom slugs: reserved words rejected (incl. case-insensitive `API`), leading dash/unicode/space/65-char rejected, 64-char ok, collision 400; live concurrency test: 8 parallel same-slug creates → exactly one 201, rest rejected, slug serves winner. - Burn-after-N: budget correct for cookie viewers (dedupe window), distinct viewers each consume one read, no-cookie clients count as their own viewer and burn correctly; burned paste → 404 on view/raw/API. Password+burn works via unlock form and API password. Expiry+burn both displayed. - Expired pastes 404 on view/raw/API. Unlisted: not in /api/public, viewable, in creator's /api/mine. Soft-deleted paste disappears from /api/mine immediately; offset beyond total → empty list not error; limit=0/-5/10⁶/abc all clamp safely. - XSS-safety of titles through create→history→view pipeline (quotes, angle brackets, emoji) — all properly escaped; title round-trips through API unchanged. - Highlighted vs unknown languages, blank lines, trailing newline, 5000-char single line, CRLF content, emoji — gutter counts and rendering correct. 5MB paste: create 1.1s, raw 0.4s, HTML view instant. - Unlock flow: wrong password → rate-limited after 5 tries (429 includes correct-password attempts, acceptable), valid unlock sets per-paste HMAC cookie, subsequent GETs unlocked. Direct /settings & /mine without cookie render fine. ## Quirks noted (not fixed, by design or low value) - /raw on a password-protected paste returns 401 with no unlock path (raw is meant for password-less pastes). - Burned/expired paste IDs are never reused; deletion token hard-delete works and also frees nothing (ids unique random). - `GET /{id}/` with trailing slash → 404 (chi default). - Duplicate parallel DELETEs of the same paste all return 200 (idempotent soft-delete) — harmless. Test pastes cleaned up (72 removed, DB back to pre-sweep seeded state). `go test ./...` passes; service rebuilt, restarted and verified live.
Sign in to join this conversation.