Files
clienthub/.planning/security/SECURITY-REMEDIATION-PLAN.md
simone f7eb7eec23 docs(planning): archivia v2.1/v2.2/v2.3 e documenta v2.4
.planning/ documentava in dettaglio cio che era vecchio e per niente cio
che e in produzione: le fasi 11-22 (v2.1 e v2.2, chiuse a giugno) erano
ancora in phases/ mentre v1.0 e v2.0 stavano gia in milestones/, e il
lavoro degli ultimi due mesi - gate OTP e ciclo di vita dei retainer, cioe
quello che gira su hub.iamcavalli.net - non aveva nessuna cartella.

- phases/{11,12,14} -> milestones/v2.1-phases/, phases/{18..22} ->
  milestones/v2.2-phases/. Ora phases/ contiene solo la milestone in
  corso, che e quello che state.cjs conta per il progresso
- v2.1-ROADMAP.md ricostruito: era l'unica milestone senza archivio,
  interrotta dal reset del 19/06 e mai chiusa formalmente
- v2.3-ROADMAP.md + v2.3-REQUIREMENTS.md: v2.3 e stata eseguita fuori dal
  ciclo GSD, non esistono PLAN/SUMMARY per fase. L'archivio E la doc
- REQUIREMENTS.md riscritto per v2.4 con il backlog reale
- phases/13 e phases/26: SUMMARY ricostruiti da commit, migration e
  STATUS.md. 26 e il primo numero libero
- research/: cancellate 4 varianti dello stesso PITFALLS e FEATURES/
  SUMMARY, superati da PROJECT.md. Diverse anti-feature erano ormai
  contraddette dai fatti (il Kanban e stato costruito in Phase 19,
  l'email in v2.3, il time tracking esiste)
- cancellati UI-RULES.md e DESIGN-SYSTEM.md (CLAUDE.md li dichiara
  superseded: impongono l'inverso della regola attuale) e HANDOFF.md,
  fermo al 13/06
- SECURITY-*.md -> security/: audit chiuso, ma i report restano la doc di
  cosa e stato ruotato e perche
- PROJECT.md/MILESTONES.md/ROADMAP.md allineati: milestone corrente v2.4,
  sessione OTP 90gg non 30, migrazioni fino alla 0016

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-08 22:38:36 +02:00

7.8 KiB
Raw Permalink Blame History

Piano di remediation sicurezza — ClientHub

Data: 2026-07-27 · Base: .planning/SECURITY-SCAN.md + .planning/SECURITY-AUDIT-INFRA.md Stato: APPROVATO ed ESEGUITO per la parte codice (2026-07-27). Restano aperti solo i punti che richiedono accesso a Coolify o una tua decisione — vedi in fondo.

11 findings totali: 2 critici, 4 alti, 3 medi, 2 bassi.

Stato di esecuzione

# Intervento Stato
0.1 Ruotare password Postgres prod non serve — la password leakata risulta già morta (vedi correzione in INFRA §1)
0.1b Espurgare la password dai due 07-01-SUMMARY.md fatto
0.2 INTERNAL_SECRET in Coolify fatto 2026-07-28 — le route interne ora danno 403
0.3 Allungare ADMIN_PASSWORD fatto 2026-07-28 — 15 → 32 caratteri
1.1 Next → 16.2.12, next-auth → 4.24.15 fatto
1.2 Secondo gate di autenticazione admin fatto
1.3 Eliminare src/lib/quote-actions.ts fatto
1.4 Rate limit su /client/ fatto
2.1 Slug a 12 caratteri + CSPRNG fatto (solo nuovi clienti)
2.2 Rigenerare gli slug esistenti fatto — 4 clienti ruotati in prod il 2026-07-28
3.1 HSTS fatto
3.2 CSP fatto (enforcing; script-src con unsafe-inline, motivato nel file)
3.3 Rimuovere i 4 sink dangerouslySetInnerHTML fatto
3.4 Delimitare i transcript nel prompt fatto
3.5 Potatura della Map di rate-limit.ts fatto

Verifiche eseguite: tsc --noEmit pulito · npm run build OK (32 route) · eslint pulito sui file toccati · smoke test su /admin/login (rende), /admin (307 → login), header forgiati (307 → login, non servono la pagina) · CSP e HSTS presenti nella risposta.

npm audit: da 12 vulnerabilità (1 critica, 5 alte) a 10 (0 critiche, 5 alte). Le 9 CVE dirette di Next.js — incluso il proxy bypass — sono chiuse; il flag next residuo è solo transitivo via postcss/sharp vendorizzati dentro Next, e l'unico "fix" che npm propone è il downgrade a Next 9. js-yaml e brace-expansion restano ma sono solo toolchain di sviluppo.


Ordine di esecuzione

Ordinato per rischio ora, non per difficoltà. I lotti 0 e 1 chiudono tutto ciò che è attivamente sfruttabile oggi.


LOTTO 0 — Rotazione segreti (nessun codice, solo credenziali) · ~30 min

Va per primo perché è l'unico finding dove il segreto è già uscito dal perimetro.

0.1 — Ruotare la password Postgres di produzione (INFRA §1)

  • Nuova password su Postgres prod via ssh + docker exec ... psql (ALTER USER clienthub WITH PASSWORD ...)
  • Aggiornare DATABASE_URL nelle env var di Coolify → redeploy
  • Aggiornare .env.local locale
  • Espurgare la stringa dai due file .planning/**/07-01-SUMMARY.md e committare
  • ⚠️ La credenziale resta nella storia di git: la rotazione è ciò che la neutralizza, la cancellazione del file no. Non serve riscrivere la storia se la password è cambiata.

0.2 — Impostare INTERNAL_SECRET in Coolify produzione (INFRA §2)

  • Aggiungere la env var (valore già presente in .env.local, 44 char) → redeploy
  • Verifica di accettazione: curl -sI 'https://hub.iamcavalli.net/api/internal/validate-slug?slug=x' deve rispondere 403, non 404. Oggi risponde 404.

0.3 — Allungare ADMIN_PASSWORD (INFRA §7) — oggi 14 char, il .env.example prescrive ≥20. È l'unico fattore di autenticazione admin.

Rischio dati: nullo. Nessuna migrazione, nessuno schema toccato.


LOTTO 1 — Chiudere lo sfruttabile · ~2 ore

1.1 — Aggiornare Next.js a ≥ 16.2.11 e next-auth a ≥ 4.24.15 (INFRA §3, §4)

  • Chiude il proxy-bypass GHSA-6gpp-xcg3-4w24 (che è il moltiplicatore di C-1), la disclosure degli endpoint delle Server Function, 2 SSRF, 2 cache-confusion, 3 DoS, e il crash di getToken() su header Authorization malformato.
  • Update mirati, non npm audit fix --force: quel comando tenta di degradare drizzle-kit a 0.18.1 (downgrade major) e romperebbe le migrazioni.
  • Trascina anche i fix di postcss e sharp.
  • Serve un npm run build + smoke test su login admin e una dashboard cliente.

1.2 — Rendere admin/layout.tsx un guard vero (C-1) — una riga:

if (!session) redirect("/admin/login");

Da solo trasforma il singolo punto di rottura in due livelli indipendenti. Anche con il proxy aggiornato, questo è ciò che rende il sistema robusto al prossimo bug del proxy.

1.3 — Eliminare src/lib/quote-actions.ts (C-3) — codice morto, zero chiamanti, due endpoint pubblici non autenticati di cui uno scrive su DB. Cancellazione secca. In alternativa conservativa: requireAdmin() in testa a entrambe le funzioni.

1.4 — Rate limit sul ramo /client/ in src/proxy.ts (C-2) — oggi il rate limiter è applicato solo a /quote/. Toglie la possibilità di brute-forzare gli slug a velocità utile.

Rischio dati: nullo. Nessuna migrazione.


LOTTO 2 — Rinforzare gli slug · ~2 ore · ⚠️ tocca dati esistenti

2.1 — Portare il suffisso di toSlug() da 4 a 12 caratteri casuali (C-2)

  • src/app/admin/clients/new/actions.ts:22-30
  • Da 36⁴ ≈ 1,7 milioni a 36¹² ≈ 4,7 × 10¹⁸.

2.2 — Rigenerare gli slug dei clienti esistenti

  • ⚠️ Rompe i link già inviati ai clienti. Da decidere insieme:
    • (a) rigenerare tutto e reinviare i link, oppure
    • (b) lasciare gli slug esistenti e affidarsi al rate limit di 1.4 come mitigazione.
  • Migrazione puramente additiva su una colonna esistente (UPDATE clients SET slug=...): nessun DROP, nessun TRUNCATE, conforme al vincolo Data Safety di CLAUDE.md.
  • Questa è una decisione tua, non mia. Il lotto 2 non parte senza una risposta su (a) vs (b).

LOTTO 3 — Difesa in profondità · ~2 ore

3.1 — Strict-Transport-Security in next.config.ts (INFRA §6) max-age=63072000; includeSubDomains; preload. Conta più del normale qui: i token cliente viaggiano nell'URL, quindi un downgrade attivo li espone in chiaro.

3.2 — Content-Security-Policy (INFRA §6) — richiede un nonce per lo script inline di tema in src/app/layout.tsx:38. Da introdurre prima in Report-Only per una settimana, poi in enforcing.

3.3 — Sanificare i quattro sink dangerouslySetInnerHTML (C-4) in src/components/public/proposal/sections/. Dato che serve solo grassetto/corsivo, la strada più pulita non è aggiungere una libreria di sanitizzazione ma togliere il rendering HTML e sostituirlo con un formatter a whitelist di tag.

3.4 — Delimitare i transcript nel prompt di src/lib/proposal/agent.ts:37-40, così che il contenuto fornito da terzi non possa essere confuso con istruzioni.

3.5 — Potare la Map di src/lib/rate-limit.ts (INFRA §7) — oggi cresce di una entry per IP distinto e non viene mai svuotata: crescita di memoria non limitata nel container.


Cosa NON propongo di fare

  • Non riscrivere la storia di git per il leak §1: la rotazione neutralizza la credenziale, e un filter-branch su un repo con storia condivisa costa più di quanto renda.
  • Non toccare drizzle-kit / esbuild: le uniche vulnerabilità restanti sono di sola toolchain di sviluppo, non raggiungibili in produzione, e il "fix" è un downgrade major.
  • Non introdurre 2FA sul login admin in questo giro: è un cambio di prodotto, non una correzione. Da valutare insieme alla feature Email OTP già a design.

Lotto Contenuto Tempo Migrazioni Rischio dati
0 Rotazione segreti ~30 min no nullo
1 Update + guard + dead code + rate limit ~2 h no nullo
2 Rinforzo slug ~2 h UPDATE additivo richiede tua decisione
3 HSTS, CSP, XSS, prompt, memoria ~2 h no nullo

I lotti 0 e 1 chiudono tutto ciò che è sfruttabile oggi e non toccano un solo dato.