Files
plm-lottery/BUGS.md
T
davideandClaude Sonnet 5.5 c1b192f6d1 Harden the container: non-root, pinned images and dependencies, the key in a file (B-91, SEC-51, SEC-52, SEC-53; closes B-71, B-72)
- app runs as uid 1000; a one-shot data-permissions service chowns ./data to it
  and closes it to others (works on an older root-owned install with no host sudo);
  files are created 0600 (umask 077)
- python:3.12-slim and caddy:2-alpine pinned by digest; dependencies installed from
  requirements.lock with --require-hashes
- the Fernet key is XPRV_ENCRYPTION_KEY_FILE (./secrets/xprv_encryption.key, a compose
  secret), out of every environment variable and of a ./data backup
- setup.md no longer presents a second way to run the server (B-72)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-18 00:54:23 +02:00

4.4 KiB

BUGS.md — open findings

Only the 2026-09-28 specification review (B-73 … B-100) still has open entries (B-92, B-93, B-94, B-96). A finding is removed from this file once it is fixed, and git log --all --grep 'B-nn' is the record of how it was closed. Per CLAUDE.md's convention each entry gets its own commit with its own regression test, and the B-nn marker goes in a comment next to the fix.

Audit of 2026-09-28 — specification review (B-73 … B-98)

Fourth audit, run against the new specification in docs/ rather than the code alone: four independent reviews (coin/chain mechanics, money state machine, DevOps, spec cross-validation), with the critical claims re-verified in code. The target behaviour of every entry below is now written into docs/ as requirements marked *Stato:* da implementare (B-nn); the Italian overview, the design decisions and the implementation order are in docs/piano-correzioni.md. The code has not been changed yet. Same convention as above: one commit per entry, with its own regression test, spec + code + test together.

Design decisions taken with the owner on 2026-09-28 (they change existing decisions, see docs/limiti-noti.md):

  • Draw by commit-reveal. The winner must not be computable by anyone who controls block production, since PLM's hashrate (~22 TH/s) is rentable.
  • Never release funds on doubt. A money-moving row is rolled back only on proof (an explicit rejection, or its inputs confirmed-spent elsewhere), never on a timeout or on one server's silence.
  • One pool address per round, so a round's money, history and payout are isolated from every other round's.
  • Deposits need 6 confirmations (configurable), everything else keeps 1: the deposit is the only input a user can double-spend.
  • Contain reorgs instead of pretending they don't happen: detect them, pause on deep ones, and pay out only what is verifiably on chain.

Medium

B-92 — No health, readiness or real audit browsing

/health always answers ok; nothing exposes Electrum state; the audit endpoint returns only the latest 500 rows unfiltered; registration writes no audit row; the access log records Caddy's IP; app logs go only to the file.

Fix: GET /health/ready (Electrum connected, tip age < 1800s, scheduler heartbeat < 30s) used as the container healthcheck; Electrum status in /admin; audit filters by event type, round, user and time, with pagination; user_registered audit row with index and address; uvicorn proxy headers from Caddy only; logs also to stdout.

B-93 — Deploys and rollbacks

A deploy with an open SSE stream never shuts down gracefully (uvicorn waits, Docker kills after 10s); an older image cannot start on a newer DB; images are untagged; SITE_ADDRESS passed per command reverts to localhost on the next plain docker compose up.

Fix: close SSE streams on shutdown, stop_grace_period: 30s, await task cancellation; tag images per release; SITE_ADDRESS in .env with ${SITE_ADDRESS:?}; documented pre-deploy backup and downgrade procedure.

B-94 — Account and endpoint abuse limits

change-password has no rate limit, no audit and no maximum length; login fields have no length bounds; POST /bug-reports is anonymous and unlimited; GET /admin/users has no pagination; admin, SSE and framework errors are bare strings; static JS/CSS have no Cache-Control.

Fix: throttle and audit (password_changed) the password change, cap every credential field at 256, rate-limit bug reports per IP, paginate the user list, structured errors everywhere, Cache-Control: no-cache on static files.

B-96 — Smaller state inconsistencies

A reduced full-balance withdrawal is not re-checked against the minimum; an abandoned payout leaves winner_amount_sats/fee_amount_sats set; a promoted payout writes no payout_sent and a promoted bet no bet_placed; the reconciler pings SSE for every row it checks; concurrent refreshes of one user can collide on the unique outpoint and end the Electrum session; listener.client is read outside the per-user lock; a bet covered only by pending change gets insufficient_balance instead of balance_pending_confirmation. A round waiting in closing on a bet that never leaves the mempool gives no signal at all.

Fix: each as described, one regression test per item; for the closing wait, a closing_stalled audit row every 1800s past the deadline and closing_waiting_since on GET /rounds/current (spec: TX-29).