Security pentest sweep #34
Notifications
Total Time Spent: 14 seconds
poslop
14 seconds
No due date set.
Dependencies
No dependencies set.
Reference: poslop/palette#34
Reference in New Issue
Block a user
Adversarial security review of the running app and codebase. Attack surfaces: API input validation (oversized payloads, malformed JSON, type confusion), SQL injection (all queries, esp. search/sort params), XSS (paste content rendering, title, custom slug reflected anywhere, stored vs reflected), path traversal (static handler, raw endpoints), auth bypass (password cookie forging, deletion token guessing/brute force, burn-after-read races), CSRF on state-changing endpoints, rate limiting absence (brute force password pastes), header handling (CSP, content-type sniffing on /raw), information disclosure (error messages, deletion tokens in URLs/logs, timing attacks on password/token compares). Document findings with severity, fix critical ones in-sweep, file the rest.
poslop referenced this issue2026-09-09 01:56:37 +00:00
Security pentest sweep complete. Findings (live-verified against localhost:8080):
CRITICAL — Auth bypass: forgeable password unlock cookie — FIXED (
cb23707)The
pw_<id>cookie was a static value1checked withc.Value != "1". Anyone could read ANY password-protected paste by sendingCookie: pw_<paste-id>=1. Repro: create password paste →curl -H 'Cookie: pw_<id>=1' /<id>→ full content rendered. Fix: cookie value is now an HMAC-SHA256 of the paste id under a per-instance random secret (PALETTE_UNLOCK_SECRETenv override for multi-instance deploys) —unlockToken()in web.go. Tests: pentest_cookie_test.go.CRITICAL — Stored XSS: attacker-controlled content_type on /raw — FIXED (
cb23707)content_typein the create API was served verbatim from/raw/{id}. A paste stored withtext/html(verified live) orimage/svg+xmlrenders as active script on this origin when anyone opens the raw URL. Repro:POST /api/pastes {"content":"<script>alert(1)</script>","content_type":"text/html"}→GET /raw/<id>served withContent-Type: text/html. Fix: fixed allowlist (safeRawContentType) — text/plain, markdown, json, pdf, raster images, octet-stream; everything else downgraded totext/plain; charset=utf-8withX-Content-Type-Options: nosniff. Same guard applied to/api/cans/{id}/items/{item}. Tests: pentest_rawct_test.go.MED — Burn-after-read race window — OPEN (accepted for single-user beta)
Concurrent reads of a
burn_after_read: truepaste can all succeed before any SoftDelete lands (verified: 4/6 concurrent API readers received content). The check-then-delete inregisterReadis not atomic. Practical impact low (paste is deleted right after; concurrent fetchers within the same instant may each get one copy). Proper fix = atomic conditionalUPDATE pastes SET deleted_at=? WHERE id=? AND deleted_at IS NULLchecked via RowsAffected before returning content, or a reads_used conditional UPDATE guard.MED — No security headers on HTML pages — OPEN
No CSP, X-Frame-Options, or Referrer-Policy on any rendered page. Rendering is safely escaped (verified: pasted HTML/script content is inert on the paste page, titles/slug/history all escaped), so CSP would be defense-in-depth, not a fix for an active bug. Suggest a middleware:
default-src 'self'; script-src 'self' 'unsafe-inline'(page scripts are inline) +frame-ancestors 'none'+Referrer-Policy: no-referrer.LOW — CORS: no /api responses declare Access-Control-Allow-Origin — OPEN (safe by default)
Simple cross-origin GETs are still readable via no-cors fetches only for opaque responses; JSON API responses are not readable cross-origin since ACAO is absent. DELETEs from other origins would need preflight (no ACAO → blocked). No action required; document that any future CORS headers must be origin-allowlisted.
LOW — expires_in accepts negative/zero durations — OPEN
-1h/0screate already-expired pastes (deleted by sweeper within a minute). No DoS or data exposure; cosmetic. Also accepted87600h(10y) — a cap (e.g. 1y, as the UI enforces) would be nice server-side.Verified NOT exploitable:
Build green (
go build,go test ./...ok), deployed viasystemctl --user restart palette, fixes committed/pushed ascb23707. Test pastes created during the sweep were deleted.Findings split into followup issues: #58 (burn race, MED), #59 (security headers, MED), #60 (expires_in clamp, LOW). Both CRITICALs fixed in
cb23707and deployed in v0.2.0. Non-exploitable items verified and documented in the report above. Closing as complete.