Files
clienthub/.planning/SECURITY-REMEDIATION-PLAN.md
T
simone bcff4aad48 docs(security): slug cliente ruotati in produzione
I 4 slug esistenti avevano suffissi da 4 caratteri generati con Math.random();
ora 12 caratteri CSPRNG. Lo slug risolve prima del token, quindi era il vero
anello debole dell'accesso alla dashboard cliente (C-2).

Lo script SQL NON e' committato di proposito: contiene gli slug in chiaro, che
sono credenziali bearer verso /client/<slug> — committarlo ripeterebbe la fuga
di credenziali trovata al punto 1 dell'audit.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-28 10:55:38 +02:00

7.7 KiB
Raw 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 richiede te (no accesso Coolify)
0.1b Espurgare la password dai due 07-01-SUMMARY.md fatto
0.2 INTERNAL_SECRET in Coolify richiede te
0.3 Allungare ADMIN_PASSWORD richiede te
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.