82 Commits
Author SHA1 Message Date
poslop 7ca1b58362 Merge pull request 'docs: attachments & storage backend design (#38, #31)' (#69) from issue-38-31-storage-design into main
CI / test (push) Successful in 19s
CI / docker (push) Successful in 34s
v0.2.1
2026-09-09 14:27:50 +00:00
poslop 8d0825ac09 Merge pull request 'docs: cookie-based preferences and access keys design (#30)' (#70) from issue-30-cookie-design into main
CI / test (push) Successful in 19s
CI / docker (push) Skipped
2026-09-09 14:27:14 +00:00
poslop 4cb8a3e127 Merge pull request 'Search: loading indicator and performance note (#32)' (#72) from issue-32-search-indicator into main
CI / test (push) Successful in 30s
CI / docker (push) Skipped
2026-09-09 14:26:25 +00:00
poslop 91dbf6f344 Merge pull request 'fix: input validation gaps (#68)' (#78) from issue-68-input-validation into main
CI / test (push) Successful in 23s
CI / docker (push) Skipped
2026-09-09 14:26:19 +00:00
poslop b600cdf51a Merge pull request 'Admin key endpoint: per-IP rate limiting and lockout (#66)' (#73) from issue-66-admin-ratelimit into main
CI / test (push) Successful in 24s
CI / docker (push) Skipped
2026-09-09 14:26:13 +00:00
poslop 47c9fa6386 Merge pull request 'Clamp expires_in at API boundary (#60)' (#74) from issue-60-expiry-clamp into main
CI / test (push) Successful in 23s
CI / docker (push) Skipped
2026-09-09 14:26:07 +00:00
poslop b7d1119d6d Merge pull request 'docs: design for optional client-side E2E encryption (issue #39)' (#75) from issue-39-e2e-design into main
CI / test (push) Successful in 20s
CI / docker (push) Skipped
2026-09-09 14:26:02 +00:00
poslop 44fe3c5772 Merge pull request 'Security headers middleware: CSP, Referrer-Policy, nosniff (#59)' (#76) from issue-59-security-headers into main
CI / test (push) Successful in 21s
CI / docker (push) Skipped
2026-09-09 14:26:01 +00:00
poslop 556d0fa2e1 Merge pull request 'fix #63: require deletion token on DELETE /api/pastes/{id}' (#79) from issue-63-delete-auth into main
CI / test (push) Successful in 18s
CI / docker (push) Skipped
2026-09-09 14:25:40 +00:00
poslop bdd5235f2e Merge pull request 'fix: atomic burn-after-read claim (#58)' (#77) from issue-58-burn-race into main
CI / test (push) Successful in 18s
CI / docker (push) Skipped
2026-09-09 14:25:06 +00:00
poslop 91568c0598 fix #63: require deletion token on DELETE /api/pastes/{id}
CI / test (pull_request) Successful in 24s
CI / docker (pull_request) Skipped
- DELETE now demands the create-time deletion token (Authorization
  header: Bearer/Token/bare, or ?token= query param), compared with
  the constant-time store.DeletionTokenEqual. 403 otherwise.
- Creator-browser deletes via the /mine button (matching vwr cookie,
  #37) remain allowed; other browsers and plain API clients get 403.
- Regression tests: no token, wrong token (header+query), correct
  token (header+query), creator-cookie path, token extraction.
- Adapted TestSoftDelete to pass the deletion token.
- docs/API.md delete section updated.
- Based on #58's SoftDelete (bool, error) signature.
2026-09-09 09:21:28 -05:00
agent 94a4a3c2ec fix: input validation gaps (#68)
CI / test (pull_request) Successful in 24s
CI / docker (pull_request) Skipped
- enforce server-side body cap via http.MaxBytesReader: oversized JSON
  bodies are rejected with 413 instead of being fully decoded first
- negative burn_after_reads rejected with 400 (zero still = default 1)
- limit=0 explicitly maps to default page size; shared parseLimit clamp
  for /api/public and /api/mine (huge/non-numeric values too)
- negative/non-numeric offset clamped to 0 via parseOffset (was
  pass-through)
- regression tests in issue68_validation_test.go

expires_at/expires_in validation intentionally excluded: covered by #60.
2026-09-09 09:21:25 -05:00
agent 78374b2d49 fix: atomic burn-after-read claim (#58)
CI / test (pull_request) Successful in 20s
CI / docker (pull_request) Skipped
SoftDelete now reports whether it performed the delete (conditional
UPDATE ... WHERE deleted_at IS NULL checked via RowsAffected).
RegisterRead returns an admitted flag: legacy burn pastes admit exactly
one reader (the atomic soft-delete winner), and burn-after-N pastes
increment reads_used via a conditional UPDATE guarded on
reads_used < reads_limit, so concurrent readers cannot both consume the
final read. API, HTML, and raw read paths return 404 when the reader
loses the burn claim; content is never served twice.

OpenStore pins the SQLite pool to one connection: concurrent writes on
separate pooled connections surfaced SQLITE_BUSY as spurious 500s
instead of serializing.

Adds concurrency regression tests: 24 parallel readers of a burn paste
(exactly one receives content, none of the others leak it) and 30
parallel readers vs a 3-read budget (exactly 3 admitted, then 404).
2026-09-09 09:18:42 -05:00
palette-agent e08cafe9c8 security headers middleware: CSP, Referrer-Policy, nosniff on HTML pages (#59)
CI / test (pull_request) Successful in 30s
CI / docker (pull_request) Skipped
- web.SecurityHeaders middleware wired into the chi router
- Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline' (page scripts are inline); frame-ancestors 'none'
- Referrer-Policy: no-referrer, X-Content-Type-Options: nosniff
- Applied only to text/html responses; JSON API and /raw pass through unchanged
- Regression test internal/web/securityheaders_test.go
2026-09-09 09:16:42 -05:00
Hermes Agent 2159982795 docs: design for optional client-side E2E encryption (issue #39)
CI / test (pull_request) Successful in 25s
CI / docker (pull_request) Skipped
2026-09-09 09:16:13 -05:00
fen bb5c4f0186 fix #60: apply ValidExpiry clamp to cans create handler (was lost in working tree)
CI / test (pull_request) Successful in 27s
CI / docker (pull_request) Skipped
2026-09-09 09:15:39 -05:00
palette-agent 9811191648 admin: per-IP rate limit (5/min) on admin key attempts (#66)
CI / test (pull_request) Successful in 23s
CI / docker (pull_request) Skipped
2026-09-09 09:15:01 -05:00
poslop 5a2aa0f96d docs: design note for cookie-based preferences and access keys (#30)
CI / test (pull_request) Successful in 21s
CI / docker (pull_request) Skipped
2026-09-09 09:14:25 -05:00
poslop c288fc73a7 Search: spinner indicator + performance note on client-side filtering (#32)
CI / test (pull_request) Successful in 19s
CI / docker (pull_request) Skipped
- Fix filter fetch to request limit=100 (API max) instead of 500, which
  the API silently clamped, so filtered results actually cover the fetch window.
- Document client-side filtering behavior and limits in README Performance Notes.
2026-09-09 09:13:40 -05:00
Hermes Agent f6c4342468 Exclude password-protected pastes from public listings (#65)
CI / test (pull_request) Successful in 20s
CI / docker (pull_request) Skipped
ListPublic and its COUNT query now filter password_hash IS NULL, so
/api/public (and any page backed by it) no longer leaks metadata
(title, slug, existence) of password-protected pastes. Unlisted
pastes were already excluded. Adds regression test covering both.
2026-09-09 09:13:26 -05:00
poslop f611cc3e9e fix #60: clamp expires_in on cans API (400 on zero/negative, cap 1yr) 2026-09-09 09:13:21 -05:00
poslop 594b5f01aa docs: attachments & storage backend design (#38, #31)
CI / test (pull_request) Successful in 17s
CI / docker (pull_request) Skipped
- attachments: filesystem-on-PVC recommended, blob interface keeps MinIO
  as a later drop-in; size limits via admin setting; sniffed-mime +
  nosniff/sandbox serving rules
- storage backend: stay SQLite-only (WAL, modernc); no Postgres/Redis and
  no backend abstraction until documented trigger conditions fire
2026-09-09 09:10:15 -05:00
poslop 03bf327f6b fix: anchor gitignore binary pattern, track cmd/palette
CI / test (push) Successful in 22s
CI / docker (push) Successful in 44s
CI / test (pull_request) Successful in 21s
CI / docker (pull_request) Skipped
v0.2.0
2026-09-09 02:04:03 -05:00
poslop a604c5906a fix: track cmd/palette (was untracked after refactor), untrack committed binary
CI / test (push) Successful in 17s
CI / docker (push) Failing after 22s
2026-09-09 02:02:57 -05:00
poslop 2c1b2818a8 CI: use static docker CLI download (no apt package in bookworm)
CI / test (push) Successful in 18s
CI / docker (push) Failing after 22s
2026-09-09 01:53:30 -05:00
poslop b1187e061f CI: install docker CLI in docker job (debian image lacks it)
CI / test (push) Successful in 19s
CI / docker (push) Failing after 30s
2026-09-09 01:45:53 -05:00
poslop 4f1e901f04 Refactor: split monolith into cmd/palette + internal/{store,api,web,lang} (#35)
CI / test (push) Successful in 19s
CI / docker (push) Failing after 2m7s
2026-09-09 01:33:39 -05:00
poslop a3349b4a98 Admin endpoint: ENV/file admin key, --reset-admin-key, settings API wired into ratelimit/max-bytes/default-expiry/burn-window (#40)
CI / test (push) Successful in 22s
CI / docker (push) Skipped
2026-09-09 00:44:02 -05:00
poslop 03600b2ed5 sweep: fix missing view_count on HTML views, API expiry bounds, search ignoring custom slug (#33)
CI / test (push) Successful in 21s
CI / docker (push) Skipped
2026-09-09 00:34:24 -05:00
poslop cb23707125 pentest: bind unlock cookie to HMAC per-paste token; serve only safe content types on /raw and can items with nosniff (#34)
CI / test (push) Successful in 21s
CI / docker (push) Skipped
2026-09-09 00:14:01 -05:00
poslop 15ce7ff011 Fix unclosed composeCustomExpiry brace that broke page JS (#48)
CI / test (push) Successful in 21s
CI / docker (push) Skipped
2026-09-08 23:58:54 -05:00
poslop 238dc96454 README rework: user-first structure, features, screenshots, config table; API detail moved to docs/API.md (#47)
CI / test (push) Successful in 19s
CI / docker (push) Skipped
2026-09-08 23:54:54 -05:00
poslop d5a47b1a31 Burn after N reads: reads_limit/reads_used, per-viewer 15min dedupe via paste_views, reads_remaining in API+stats pill, raw counts as read (#49)
CI / test (push) Successful in 21s
CI / docker (push) Skipped
2026-09-08 23:54:03 -05:00
poslop 127c12c79a Custom expiry: Custom radio with number+unit select, client-side validation 1min-1year, Go-duration composition (#48) 2026-09-08 23:54:03 -05:00
poslop 2e1ce508fa Saved page: live search + sortable columns via shared table module (#57)
CI / test (push) Successful in 21s
CI / docker (push) Skipped
- new web/static/table.js (PaletteTable): shared live search, client-side
  sort with indicators, pagination, row rendering + click-through
- history.html and mine.html both consume it; data endpoint, columns,
  empty-state text and row extras (delete buttons) are page-supplied
- verified in-browser: search, sort, delete on /mine; sort, pager on /history

Closes #57
2026-09-08 22:39:41 -05:00
poslop 76d7ac12e7 Small fixes: gutter alignment, paste column, sort arrows, copy feedback, stats bar, title slug (#50-#55)
CI / test (push) Successful in 21s
CI / docker (push) Skipped
- #50 gutter/code line misalignment: shared --code-lh/--code-fs tokens;
  .code .gutter drops its own vertical padding (was 14px vs 0, 14px offset)
- #51 history Paste column shows only the paste name, or slug pill when untitled
- #52 sort indicator arrow sits LEFT of the column label for all sortable columns
- #53 copy buttons give in-place success feedback ('Success!' in --ok for 2s):
  paste view copy button and new-page result copy button
- #54 collapsed stats summary bar: roomier padding (14px 18px) and item gap (16px);
  wraps gracefully on mobile
- #55 paste view title bar: slug pill only shown for custom slugs, id not repeated

Closes #50, closes #51, closes #52, closes #53, closes #54, closes #55
2026-09-08 22:35:43 -05:00
poslop 129934b645 My pastes page /mine with anonymous viewer cookie (#37)
CI / test (push) Successful in 21s
CI / docker (push) Skipped
- vwr cookie middleware: random browser id set on first visit (reused by #49)
- pastes table gains viewer_id column, set server-side at creation from the cookie
- GET /api/mine lists pastes for the requesting browser (title/lang/size/created)
- DELETE enforcement: 403 when client-sent vwr doesn't match the paste's viewer_id
- /mine page reuses history table styling, delete buttons, empty state
- nav: 'Saved' item between Public and Git; Git gets external-link arrow (#56)
- tests: create-with-cookie appears in /mine, other cookie doesn't, delete enforcement

Closes #37
2026-09-08 22:10:05 -05:00
poslop e83303a428 Consistency audit: shared input/button/heading tokens across pages (#18)
CI / test (push) Successful in 21s
CI / docker (push) Skipped
- One text-input treatment (size, color, placeholder) for .search input,
  .pw-field input, #title, #custom, .row text inputs
- Icon-only buttons (.iconbtn, .btn-icon) share .btn radius + focus ring
- Unified heading letter-spacing with .side-section h3 pattern
- Normalized settings card padding to the 12/18px wide-card scale
- Verified visually: before/after screenshots for new/history/settings/unlock

Closes #18
2026-09-08 22:00:21 -05:00
poslop acf71f7444 Palettes: pastel-lavender/peach/cloud presets + semantic status colors (#16)
CI / test (push) Successful in 21s
CI / docker (push) Skipped
- Three new light [data-preset] blocks alongside midnight (default) and smooth
- --ok/--warn/--err semantic tokens with 4.5:1 contrast per preset
- .toast success/error variants wired in new.html
- ?theme= query param preview hook in layout head (screenshots only)
- Preview screenshots in docs/palette-previews/

Closes #16
2026-09-08 21:56:36 -05:00
poslop 9c4689e6de Mobile responsiveness: media query for 375-640px across topbar, deck, history, paste view, unlock (#45)
CI / test (push) Successful in 20s
CI / docker (push) Skipped
2026-09-08 21:45:42 -05:00
poslop ebcf7eec80 Live relative-time counters via shared [data-ts] ticker in layout foot (#46)
CI / test (push) Successful in 20s
CI / docker (push) Skipped
2026-09-08 21:41:29 -05:00
poslop b3112d2a35 History: sortable column headers with client-side sort (#42)
CI / test (push) Successful in 21s
CI / docker (push) Skipped
2026-09-08 21:36:53 -05:00
poslop 6a6f4d1587 History: URL + ID columns at end, custom_slug in /api/public (#44)
CI / test (push) Successful in 20s
CI / docker (push) Skipped
2026-09-08 21:33:08 -05:00
poslop e31aa44ecb History: paste name under Paste column, drop Description column (#43)
CI / test (push) Successful in 21s
CI / docker (push) Skipped
2026-09-08 21:32:10 -05:00
poslop 5b7198fb3c Settings gear in topbar linking to placeholder /settings page (#36)
CI / test (push) Successful in 20s
CI / docker (push) Skipped
- layout.html: inline SVG gear icon top-right, styled as .iconbtn.gear pill
- new settings.html template reusing standard layout with under-construction card
- register GET /settings route in main.go
2026-09-08 21:27:02 -05:00
poslop efa8c7145c Language detection: switch to go-enry with hint-seeded classifier (#41)
CI / test (push) Successful in 35s
CI / docker (push) Skipped
- Add github.com/go-enry/go-enry/v2 dependency to go.mod
- guessLang: JSON fast path, then enry strategies (shebangs, XML, modelines,
  content heuristics), then enry's Linguist-trained Bayesian classifier over
  regex-hint candidate languages; curated dropdown expanded with typescript,
  html, css, xml, php, ruby, perl, lua, dockerfile, toml, ini, diff, csharp
- enry display names normalized to lowercase stored ids (Dockerfile->dockerfile,
  Shell->bash, C#->csharp); unsupported languages render unhighlighted
- Tests: existing languages, all new languages, magic markers (enry shebang/
  DOCTYPE/FROM/diff handling), canonical mapping
2026-09-08 21:25:36 -05:00
poslop 7410b5c9cf Paste view: collapsible stats pill under title (#17)
CI / test (push) Successful in 19s
CI / docker (push) Skipped
2026-09-08 21:14:44 -05:00
poslop 3facff3d1e Syntax highlighting, rate limiting, creator auto-unlock (#1, #2, #26)
CI / test (push) Successful in 19s
CI / docker (push) Skipped
#1: server-side regex highlighter (highlight.go) for go/python/js/json/bash/sql;
token span classes styled in app.css; per-line so gutter stays aligned.
#2: in-memory token-bucket rate limiter (ratelimit.go) on POST /api/pastes,
/api/guess-language and unlock POST; 429 + Retry-After + X-RateLimit headers.
#26: new-page JS POSTs the password to /{id} with ?next= after creation; the
unlock handler honors same-origin ?next= redirect so the creator lands on the
unlocked paste. POST /{id} route added.

Tests: ratelimit_test.go (burst/429, refill, unlock limit, highlight, auto-
unlock e2e); existing tests updated for per-test limiter isolation.
2026-09-08 21:09:25 -05:00
poslop 9d75d2f80d password reveal: slash through eye icon while password hidden
CI / test (push) Successful in 17s
CI / docker (push) Skipped
2026-09-08 20:55:25 -05:00
poslop 6b21f997b5 gitignore: exclude binary and live SQLite WAL files 2026-09-08 20:53:26 -05:00