[pentest2] burn_after_reads is never enforced: paste content remains readable indefinitely #82

Closed
opened 2026-09-09 15:40:04 +00:00 by poslop · 3 comments
Owner

Severity: High
Target: palette-dev v0.2.1

Creating a paste with burn_after_reads (verified with 1, 2, 3 and even 999999999) does not burn. Content is served on every read: /raw/{id}, /{id} HTML, and /api/pastes/{id} all return the full content on unlimited repeated reads with distinct sessions (fresh vwr cookies).

Verified repro:

create {"content":"BURN-EXPIRY-CONFIRM","burn_after_reads":1}
GET /raw/<id>   -> full content (3+ times, distinct cookies)
GET /api/pastes/<id> -> "reads_remaining": null (never populated)

20 concurrent first-reads all returned the content (not just a race — the counter never decrements). view_count increments normally; no burn state is tracked. Round-1's "burn-after-read race" fix is moot because the feature never fires at all.

Also: 20 parallel readers + repeated sequential readers all get content — the guarantee "one read" is completely broken for any security-sensitive sharing.

**Severity:** High **Target:** palette-dev v0.2.1 Creating a paste with `burn_after_reads` (verified with 1, 2, 3 and even 999999999) does not burn. Content is served on every read: `/raw/{id}`, `/{id}` HTML, and `/api/pastes/{id}` all return the full content on unlimited repeated reads with distinct sessions (fresh `vwr` cookies). Verified repro: ``` create {"content":"BURN-EXPIRY-CONFIRM","burn_after_reads":1} GET /raw/<id> -> full content (3+ times, distinct cookies) GET /api/pastes/<id> -> "reads_remaining": null (never populated) ``` 20 concurrent first-reads all returned the content (not just a race — the counter never decrements). `view_count` increments normally; no burn state is tracked. Round-1's "burn-after-read race" fix is moot because the feature never fires at all. Also: 20 parallel readers + repeated sequential readers all get content — the guarantee "one read" is completely broken for any security-sensitive sharing.
Author
Owner

Not a bug as reported. burn_after_reads (plural) is the budget for burn-after-N and only takes effect when burn_after_read: true is also set (store.CreatePaste: if p.BurnAfterRead { ... readsLimit }). Sending only burn_after_reads without the flag is a no-op by design.

Live-verified against v0.2.1 dev: create with {"burn_after_read":true} -> read1=200, read2=404. 20 concurrent readers claim verified by PR #77 regression tests (exactly one admitted).

Legit UX takeaway: the API silently ignoring burn_after_reads without the flag is confusing. Reclosing as works-as-intended but recommending the API reject or warn when burn_after_reads is set without burn_after_read.

Not a bug as reported. `burn_after_reads` (plural) is the budget for burn-after-N and only takes effect when `burn_after_read: true` is also set (store.CreatePaste: `if p.BurnAfterRead { ... readsLimit }`). Sending only `burn_after_reads` without the flag is a no-op by design. Live-verified against v0.2.1 dev: create with `{"burn_after_read":true}` -> read1=200, read2=404. 20 concurrent readers claim verified by PR #77 regression tests (exactly one admitted). Legit UX takeaway: the API silently ignoring `burn_after_reads` without the flag is confusing. Reclosing as works-as-intended but recommending the API reject or warn when `burn_after_reads` is set without `burn_after_read`.
poslop reopened this issue 2026-09-09 23:16:51 +00:00
Author
Owner

Reopening: still reproducible on dev server. POST with only burn_after_reads:1 (no burn_after_read:true) creates a normal immortal paste - reads_limit stays NULL because store.go only resolves the budget inside 'if p.BurnAfterRead'. Documented field is silently ignored.

Reopening: still reproducible on dev server. POST with only burn_after_reads:1 (no burn_after_read:true) creates a normal immortal paste - reads_limit stays NULL because store.go only resolves the budget inside 'if p.BurnAfterRead'. Documented field is silently ignored.
poslop added spent time 1 minute 2026-09-09 23:26:53 +00:00
poslop added the In Progress label 2026-09-09 23:26:53 +00:00
poslop added QA and removed In Progress labels 2026-09-09 23:28:01 +00:00
fen added spent time 1 second 2026-09-09 23:32:15 +00:00
fen deleted spent time 2026-09-09 23:32:34 +00:00
- 1 second
fen added spent time 30 seconds 2026-09-09 23:40:22 +00:00
fen closed this issue 2026-09-09 23:40:22 +00:00
Collaborator

QA passed (PR #107).

  • Diff review: minimal fix in store.CreatePaste — burn_after_reads > 0 alone now sets readsLimit and implies burn_after_read. Logic verified for all three cases (burn_after_reads alone, burn_after_read:true + reads:N, neither).
  • go vet clean; full go test ./... green, incl. new TestBurnReadsImpliedByBurnAfterReads.
  • Merged to dev; CI test run on dev: success.
  • Deployed to palette-dev (rollout complete). Live verify at https://palette-dev.archfox.org: POST {content, burn_after_reads:1} -> read1=200, read2=404, read3=404. Burn enforced.

Note: first live check hit a pod still running the pre-merge image; after rollout restart picked up the CI dev image it behaves correctly.

Closing issue and deleting fix-82-burn-implies-budget.

QA passed (PR #107). - Diff review: minimal fix in store.CreatePaste — burn_after_reads > 0 alone now sets readsLimit and implies burn_after_read. Logic verified for all three cases (burn_after_reads alone, burn_after_read:true + reads:N, neither). - go vet clean; full go test ./... green, incl. new TestBurnReadsImpliedByBurnAfterReads. - Merged to dev; CI test run on dev: success. - Deployed to palette-dev (rollout complete). Live verify at https://palette-dev.archfox.org: POST {content, burn_after_reads:1} -> read1=200, read2=404, read3=404. Burn enforced. Note: first live check hit a pod still running the pre-merge image; after rollout restart picked up the CI dev image it behaves correctly. Closing issue and deleting fix-82-burn-implies-budget.
fen added spent time 28 minutes 2026-09-10 13:57:37 +00:00
Sign in to join this conversation.
2 Participants
Notifications
Total Time Spent: 30 minutes
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: poslop/palette#82