modulo 08 / lezione 57
Pubblicare con responsabilità: sicurezza e progetto integrato
Permessi, ruoli, validazione input lato server
Evitare il classico errore dei prototipi generati: UI convincente, controlli reali insufficienti.
Permessi, ruoli, validazione input lato server
L’errore più frequente nei prototipi AI: bottone “Cancella” nascosto se non admin, ma endpoint DELETE /resource aperto a tutti. UI convincente, sicurezza zero. Sapere che ogni controllo reale vive sul server, non sul client, è prerequisito di vibe coding pubblicabile.
Perché questa lezione conta
Codice generato da AI tende a confondere “controllo UI” con “controllo di sicurezza”. Risultato: app che alla demo si comporta correttamente e in produzione viene bypassata in 5 minuti con DevTools. Non sono attacchi sofisticati: chiunque sa modificare una request HTTP. Sapere come strutturare ruoli, permessi e validazione lato server elimina questa categoria di vulnerabilità alla radice.
Obiettivo operativo
Alla fine devi saper:
- distinguere ruolo, permesso, ownership;
- progettare matrice ruoli/azioni prima di generare codice;
- validare ogni input lato server, indipendentemente dal client.
Nucleo della lezione
1. Ruoli e permessi
Ruolo: etichetta sull’utente. “admin”, “editor”, “viewer”, “owner”, “member”. Spesso più di uno per utente.
Permesso: cosa un ruolo può fare. “create_post”, “edit_any_post”, “delete_own_post”, “view_admin_dashboard”.
Mapping: ogni ruolo → set di permessi. Il codice verifica permessi, non ruoli direttamente.
Esempio:
ruoli:
admin: tutti i permessi
editor: create_post, edit_own_post, delete_own_post, view_published
viewer: view_published
permessi:
create_post: editor, admin
edit_own_post: editor (su risorsa propria), admin
edit_any_post: admin
view_admin_dashboard: admin
Vantaggio: aggiungere nuovo ruolo o cambiare permessi non richiede caccia a if (role === ...) ovunque. Centralizzato.
Ownership: chi ha creato/possiede una risorsa. Spesso permesso “edit_own_X” si applica solo se utente è owner.
canEdit(user, resource):
if user.permissions.includes('edit_any_post'): return true
if user.permissions.includes('edit_own_post') && resource.owner_id === user.id: return true
return false
2. Validazione lato server
Tre livelli di validazione, in ordine di criticità:
Validazione di forma: tipo, lunghezza, formato. Esempio: email è stringa, max 254 char, contiene @. Lib: zod, valibot, joi, yup, pydantic.
Validazione di business: regole specifiche. Esempio: prezzo > 0, data di consegna > data ordine, codice sconto valido e non scaduto.
Validazione di autorizzazione: chi sta facendo cosa, su cosa. Esempio: utente sta cercando di modificare ordine X — è suo? Ha ruolo per farlo?
I tre livelli vanno controllati TUTTI, in ordine, nella stessa request handler. Validazione assente = endpoint exploitable.
Pattern tipico:
async function updatePost(req, res) {
// 1. Auth: chi sei?
const user = await authenticate(req)
if (!user) return res.status(401).json({ error: 'unauthorized' })
// 2. Forma input
const parsed = updatePostSchema.safeParse(req.body)
if (!parsed.success) return res.status(400).json({ error: 'invalid_input', details: parsed.error })
// 3. Risorsa esiste?
const post = await db.posts.findOne({ id: req.params.id })
if (!post) return res.status(404).json({ error: 'not_found' })
// 4. Authz: puoi farlo su QUESTA risorsa?
if (!canEdit(user, post)) return res.status(403).json({ error: 'forbidden' })
// 5. Business: i nuovi dati sono validi nel contesto?
if (parsed.data.publishedAt && parsed.data.publishedAt < new Date()) {
return res.status(400).json({ error: 'cannot_publish_in_past' })
}
// 6. Operazione
const updated = await db.posts.update({ id: post.id, ...parsed.data })
res.json(updated)
}
Pattern ripetibile. Ogni endpoint sensibile lo segue. Il modello AI tende a saltare passi 1, 4, 5. Verifica.
3. Errori specifici da evitare
Lista non esaustiva ma comune:
Mass assignment: db.update({ ...req.body }). Utente manda role: 'admin' nel body, server lo accetta. Mai spread di body senza whitelist.
Insecure direct object reference (IDOR): GET /orders/:id ritorna ordine senza controllo ownership. Utente cambia id in URL → vede ordini di altri.
Privilege escalation: endpoint PATCH /users/:id permette di cambiare ruolo. Solo admin dovrebbe poter cambiare ruoli.
Errori distinguibili: 404 “user not found” vs 401 “wrong password” → enumeration. Usa stesso errore generico.
Validazione bypass: validazione client → server fida. Sempre server.
Field non chiesti accettati: schema accetta solo name, email, body contiene name, email, isAdmin. Strict mode di zod / refuse extra fields.
Caso guidato
Risorsa: documento condiviso. Ruoli:
owner: può leggere, modificare, invitare, cancellare;editor: può leggere e modificare;viewer: può leggere;guest: non può accedere.
Errore tipico AI: nasconde il pulsante “Cancella” ai non-owner, ma l’endpoint resta aperto:
app.delete("/documents/:id", async (req, res) => {
await deleteDocument(req.params.id);
res.json({ ok: true });
});
Controllo reale:
app.delete("/documents/:id", requireAuth, async (req, res) => {
const document = await getDocument(req.params.id);
if (!document) return res.status(404).json({ error: "Not found" });
const permission = await getDocumentPermission(document.id, req.user.id);
if (permission?.role !== "owner") {
return res.status(403).json({ error: "Forbidden" });
}
await deleteDocument(document.id);
res.json({ ok: true });
});
La UI può aiutare, ma non decide. Test da chiedere:
- owner cancella:
200; - editor cancella:
403; - viewer cancella:
403; - utente non loggato:
401; - documento inesistente:
404; - utente modifica campo non previsto, es.
role: "owner"nel payload: ignorato o rifiutato.
Questa matrice costringe il modello a implementare sicurezza nel posto giusto: lato server, vicino ai dati.
Procedura pratica
Per ogni endpoint:
- Authn: identifica utente (sessione/token). Niente utente → 401.
- Schema input: valida forma. Strict (no extra fields). Errore → 400.
- Risorsa: carica con id da URL. Non trovata → 404.
- Authz: utente può fare azione su risorsa? No → 403 (o 404 per evitare info leak).
- Business: regole di dominio sulla request.
- Operazione: db update/insert/delete.
- Response: minimal, niente leak di dati interni.
Per ogni feature: scrivi matrice ruoli × azioni × risorse PRIMA di generare codice. Allegala al prompt.
Laboratorio guidato
App: blog con post, ruoli admin, author, viewer.
Matrice ruoli × azioni:
- create_post: author, admin.
- read_published_post: tutti (anche non autenticati).
- read_draft_post: author (solo propri), admin (tutti).
- edit_post: author (solo propri), admin (tutti).
- delete_post: author (solo propri), admin (tutti).
- publish_post: author (solo propri, dopo review opzionale), admin.
- manage_users: admin only.
Test di sicurezza:
- author Bob crea post X → ok.
- author Alice tenta di editare post X di Bob → 403.
- viewer Mario tenta di creare post → 401/403.
- author Bob tenta di accedere a
/admin/users→ 403. - non autenticato cerca di creare post → 401.
- author Bob manda
PATCH /posts/Xcon body{role: 'admin'}(mass assignment tentativo) → ignorato (campo non in schema), solo titolo/content modificati.
Per ogni endpoint, una funzione che verifica: canCreatePost(user), canEditPost(user, post), canPublishPost(user, post). Test unit per ciascuna.
Output atteso
Per il tuo prossimo progetto:
- matrice ruoli × azioni × risorse (1 pagina);
- modulo
permissions.tscon funzionicanX(user, resource); - pattern di endpoint con i 7 step;
- test per ognuno dei “casi negativi” (utente sbagliato, risorsa altrui, ruolo insufficiente).
Studio passo passo
Studia questa lezione in tre passaggi, senza correre subito al prompt o al codice.
- Prima lettura: chiarisci l’obiettivo: evitare il classico errore dei prototipi generati: UI convincente, controlli reali insufficienti. Se non riesci a riscriverlo in una frase tua, rileggi il nucleo della lezione.
- Seconda lettura: trasforma i contenuti in domande operative:
- Ruoli e permessi: utente, admin, owner, guest, risorse proprie e risorse condivise.
- Validazione lato server come fonte affidabile; validazione lato client solo come aiuto all’utente.
- Controlli da verificare: ownership, escalation privilegi, campi non previsti, azioni dirette via API.
- Applicazione: svolgi il laboratorio: scrivere casi di accesso permesso e negato per una risorsa modificabile da più utenti. L’output da ottenere è: una matrice ruoli/azioni da includere nella specifica prima di generare backend o API.
Prima di passare alla lezione successiva, controlla di avere un artefatto concreto: una tabella, un prompt, una checklist, un mini report, un diff commentato o una decisione scritta. Nel vibe coding la comprensione resta astratta finché non produce una traccia verificabile.
Checklist di verifica
- ho matrice ruoli × azioni per il mio progetto;
- ogni endpoint sensibile esegue 7 step in ordine;
- niente mass assignment (whitelist o schema strict);
- niente IDOR (controllo ownership su risorse);
- errori distinguibili evitati su info sensibili;
- test per casi negativi presenti.
Errori comuni
Controllo solo nel client. Bottone nascosto = sicurezza zero. Endpoint deve verificare.
if (user.role === 'admin') sparso ovunque. Cambio modello permessi = caccia in tutto il codice. Centralizza.
Schema permissivo. req.body accettato in toto, campi non chiesti finiscono nel DB.
req.params.id usato senza ownership check. IDOR classico.
404 vs 403 inconsistente. Rivela esistenza di risorse non visibili. Restituisci 404 anche per “presente ma non autorizzato”.
Validazione nel handler ma non in middleware. Per endpoint multipli, middleware riusabile evita ripetizione.
Prendere ruolo dal body invece che da sessione. “Sono admin” detto dal client. Mai. Ruolo deriva da sessione/token verificato server.
Prompt di lavoro
Aiutami a progettare permessi per: [descrizione app]
Utenti tipici: [lista ruoli]
Risorse principali: [lista]
Voglio:
1. Matrice ruoli × azioni × risorse, con per ogni cella:
- permesso (sì / sì se owner / no)
- condizione speciale (es. solo se published, solo entro X giorni)
2. Modulo `permissions.ts`: per ogni azione una funzione `canX(user, resource)` con logica chiara.
3. Pattern di endpoint sicuro:
- middleware di auth
- schema input strict (no extra fields)
- check risorsa esiste
- check authz (canX)
- check business rules
- operazione
- response minimal
4. Test di sicurezza minimi: 5-7 casi negativi per ognuno dei ruoli.
Output: matrice, codice modulo permessi, pattern endpoint commentato.
NON includere ancora business logic. Solo struttura permessi.
Esercizio finale
Apri il tuo progetto. Per 3 endpoint sensibili (delete, modify, admin-only), esegui i 7 step a mente: tutti presenti? Test che simula utente non autorizzato esiste? Spesso 1 step manca su 1 endpoint. Quel “manca” è la prossima vulnerabilità in produzione. Sistema.