# Revisione Docker 2026 — Bookish (fork Docker) Revisione effettuata il 29/08/2026 su `main` (HEAD `e62c50c`). Scopo: verificare che il progetto sia **completo, sicuro e manutenibile come Docker app**, e definire un piano di miglioramento priorizzabile. --- ## 1. Sintesi esecutiva Il progetto è già una Docker app **funzionante** e ben impacchettata: - Dockerfile multistage + utente non-root + healthcheck + compose con limiti - Conversione asincrona con SSE, storage persistente su volume - Configurazione 12-factor via variabili d'ambiente La revisione ha però evidenziato **1 problema funzionale critico**, **3 falle di sicurezza** e una serie di **carenze strutturali** (test, lock, deploy) che vanno chiuse prima di considerare il progetto "pronto" per un uso reale. Esito revisione: **❌ non pronta per produzione — servono correzioni**. > Aggiornamento (sessione corrente): la P0 è chiusa (**R1, R2, R3, R6**) e > parte della P1 (**R4** lock + **CI** smoke test + **self-host JS offline**). > Restano P1 R4b e P2 (R5, R7). --- ## 2. Punti di forza (cosa tenere) 1. **Dockerfile a 2 stadi** — builder con venv separato, runtime minimale (pandoc, pango, weasyprint deps), `--no-install-recommends` e cancellazione apt-lists. 2. **Compose completo** — `no-new-privileges`, `tmpfs /tmp`, `mem_limit`, `cpus`, `restart: unless-stopped`, `stop_grace_period`, `PORT` parametrizzata. 3. **Healthcheck** `/healthz` che verifica davvero la scrivibilità del volume dati. 4. **Non-root + uid deterministico 1000** — volume riutilizzabile senza fix di permessi. 5. **Conversione asincrona** via thread + SSE con lock thread-safe e cleanup automatico anche senza cliente connesso (`generate_sse_events` con finally). 6. **Fallback localStorage** lato reader: l'app resta utilizzabile se il server API non risponde. 7. **SQLite WAL + parametrized query**: robustness accettabile per uso single-user. 8. **Documentazione** (README, docs/) completa e in italiano. --- ## 3. Debolezze — Analisi mirata ### 3.1 🔴 [CRITICO] IL Reader fallisce per una quota di file convertiti Nel modalità Docker i file vengono salvati con prefisso `uuid_`: ``` UPLOAD_FOLDER → /data/uploads/_ (con prefisso uuid) CONVERTED_FOLDER → /data/converted/. (nome = solo uuid) ``` Il problema: `bookish/routes/reader.py` cerca i file **per nome** (`UPLOAD_FOLDER/`, poi `CONVERTED_FOLDER/`), mentre il flusso reale usa: - upload: `_` — corretto, perché `stored_filename` viene passato dalla UI (funziona già). - convertito: `.ext` — il reader NON cerca mai `.ext` con il nome completo generato; cerca il `` originale, che il file convertito non ha. Quindi: - il link "Leggi nel reader" dalla UI dei **convertiti funziona** (il JS passa `stored_filename`); - ma i **link diretti `/reader/`** (es. da localStorage, `recenti`, o bookmark a mano) restituiscono **404** per il converted (il reader non risolve `` né l'originale). **Fix proposto** (P0, già coperto da test `tests/test_storage_paths.py`): 1. Nel reader, risolvere il file per il suo **stored id** applicando le due regole insieme: - cercare prima `UPLOAD_FOLDER/` (caso upload, già ok), - poi `CONVERTED_FOLDER/` (caso convertito — **aggiungere** la ricerca diretta per il nome generato `.ext`); 2. In più cercare per suffisso per non rompere i bare-name (fallback). 3. Mantenere `os.path.basename()` e la whitelist estensioni. **Stato (R1)**: ✅ fatto — il reader ora risolve i file convertiti per ID (compresi i link diretti `recent`/localStorage con bare name). ### 3.2 🟠 [Security] Nessuna autenticazione → disk DoS **Stato (R2)**: ✅ fatto in `bookish/services/security.py` — auth Basic opzionale (`BOOKISH_AUTH_USERNAME`/`BOOKISH_AUTH_PASSWORD`) con `WWW-Authenticate` su tutte le route tranne healthz/static + rate-limit in-memory su POST /upload (`BOOKISH_RATE_LIMIT_UPLOAD`, default 20/ora, 0=off). Note: - Basic su HTTP non cifra le credenziali → abilitare dietro proxy HTTPS. - Rate limit in-memory (singolo worker) → ok per uso personale; per difese robuste aggiungere limite a livello proxy. ### 3.3 🔴 [Sicurezza] XSS nel reader HTML `bookish/static/js/reader.js:165`: ```js viewer.innerHTML = text; // file HTML grezzo iniettato nel DOM ``` Un file HTML generato da Pandoc/WeasyPrint (o un file con HTML sorgente) può eseguire script nel dominio delle API. Certo è "self-hosted file non affidabili", ma un libro malevolo può esfiltrare bookmarks/cookies. Mitigazioni: 1. **CSP restrittiva**: header `Content-Security-Policy` con `default-src 'self'` e allowlist CDN per epub.js/pdf.js/jszip. I contenuti iniettati non possono eseguire `script-src` di terze parti. 2. Sanitizzazione HTML (es. `DOMPurify` client-side, o `bleach` server-side) del contenuto iniettato. 3. **Sandbox iframe**: rendere il contenutO in `