Perché non bloccare semplicemente gli agent dalle azioni sensibili?
Alcune azioni nella tua application contano più di altre. Cambiare una password, aggiungere un membro al team, esportare dati, eliminare un project, approvare un ordine: se una di queste avviene senza la giusta autorità, il danno è reale.
Allo stesso tempo, una parte sempre maggiore del lavoro legittimo nella tua app viene svolta dall'automazione. Un utente chiede a un assistant di aggiornare le impostazioni. Un'integrazione di partner crea record secondo una pianificazione. Un agent interno apre ticket di supporto. Bloccare ogni client automatizzato dai route sensibili interrompe questo lavoro e spinge le persone a tornare al lavoro manuale o a workaround più difficili da proteggere.
La risposta non è "bloccare gli agent" o "fidarsi degli agent". È un modello in cui il rischio dell'azione, l'autorità dietro la request e le evidenze sull'actor svolgono ciascuno un ruolo distinto. Questa guida costruisce quel modello. Per il quadro più ampio che lo sostiene, vedi progettare policy a livello di app per esseri umani e agent AI.
Step 1: classifica i tuoi route per rischio
Inizia classificando le azioni, non i client. Per la maggior parte delle application funziona uno schema semplice a tre livelli:
| Livello | Esempi | Requisito predefinito |
|---|---|---|
| Basso | Leggere dati pubblici, cercare, visualizzare la propria dashboard | Autenticazione normale, se presente |
| Medio | Creare o modificare i propri contenuti, invitare un viewer | Autenticato, autorizzato per la risorsa |
| Alto | Cambiare credenziali, cambiare proprietà, esportazione massiva, eliminare, approvare | Autorizzato più step-up o revisione |
Scrivi il livello nella configurazione del route in modo che sia visibile nella code review:
type Tier = "low" | "medium" | "high";
export const routeTier: Record<string, Tier> = {
"GET /projects": "low",
"POST /projects": "medium",
"POST /projects/:id/members": "medium",
"POST /account/password": "high",
"POST /exports": "high",
"DELETE /projects/:id": "high",
};I livelli cambiano lentamente e sono facili da ragionare. Tutto il resto di questa guida dipende da essi.
Step 2: in che cosa differiscono identità, credenziali e autorizzazione?
Tre domande vengono spesso confuse tra loro:
- Chi o che cosa sta agendo? Questa è l'evidenza dell'actor: una persona interattiva, una persona con assistenza AI, un agent o Unknown, con una base come Verified o Declared.
- Quale credenziale è stata presentata? Un cookie di sessione, una API key, un OAuth access token.
- Questa credenziale è autorizzata a compiere questa azione su questa risorsa? Questa è l'autorizzazione.
Una firma di agent verificata (RFC 9421) risponde bene alla prima domanda. Non dice nulla sulla terza, un punto trattato in dettaglio in cosa dimostrano le richieste firmate degli agent AI e cosa non dimostrano. Un OAuth token con lo scope corretto risponde alla terza domanda. Dice poco sulla prima. La tua decisione per un'azione di livello alto ha sempre bisogno di una buona risposta alla terza domanda e usa la prima per scegliere quanta ulteriore garanzia chiedere.
interface Context {
actor: { actor: "human" | "human_ai" | "agent" | "unknown"; basis: "verified" | "declared" | "inferred" | "unknown" };
credential: { type: "session" | "oauth" | "api_key"; scopes: string[]; subject: string };
}
function authorized(ctx: Context, action: string, resourceOwner: string): boolean {
return ctx.credential.subject === resourceOwner && ctx.credential.scopes.includes(action);
}L'autorizzazione non viene mai allentata per via di un risultato favorevole sull'actor. Il contesto dell'actor aggiunge solo requisiti o sceglie tra percorsi ugualmente sicuri.
Step 3: usa l'autorità delegata per gli agent che agiscono per conto delle persone
Quando un agent agisce per conto di un utente, forniscigli una credenziale propria con autorità limitata invece di permettergli di riutilizzare la sessione dell'utente. OAuth 2.0 è lo strumento standard: l'utente approva scope specifici per un client specifico e il token registra quel client. RFC 9700, il Best Current Practice di sicurezza per OAuth 2.0, descrive come distribuirlo in modo sicuro. I server MCP seguono lo stesso schema: la specifica di autorizzazione MCP tratta un MCP server protetto come un resource server OAuth 2.1. Il nostro articolo chi sta chiamando il tuo MCP server? applica questo ai tool call.
Questo produce esattamente ciò che serve per le azioni sensibili:
- Il token indica quale client sta agendo e per conto di chi.
- Gli scope limitano ciò che può fare. Un agent di pianificazione con
calendar:writenon può cambiare una password. - L'utente può revocare la concessione senza cambiare le proprie credenziali e OAuth 2.0 Token Revocation (RFC 7009) definisce come un client comunica all'authorization server che un token non deve più essere valido.
- I log di audit mostrano che l'agent, non l'utente, ha eseguito l'azione.
Se la tua app non supporta ancora credenziali delegate, rendile una priorità per le azioni di cui gli agent hanno più spesso bisogno. È il controllo singolo più efficace per l'accesso automatizzato.
Step 4: quando chiedere un controllo di step-up?
Step-up significa chiedere garanzie aggiuntive nel momento in cui viene eseguita un'azione sensibile. Per le API protette da OAuth, RFC 9470 definisce come un resource server comunica a un client che l'autenticazione del token corrente non è sufficientemente forte o recente. Lo step-up giusto dipende da chi sta agendo:
- Persona interattiva (Human, Human + AI): ri-autenticazione (vedi i requisiti di reauthentication in NIST SP 800-63B), un prompt passkey basato su WebAuthn o un codice monouso.
- Agent con credenziale delegata: uno scope dedicato all'azione di livello alto, concesso esplicitamente dall'utente, oppure una conferma inviata all'utente fuori banda.
- Unknown: il controllo forte già esistente del route, invariato.
type Requirement = "none" | "reauth" | "explicit_scope" | "user_confirmation" | "review";
function stepUp(tier: Tier, ctx: Context): Requirement {
if (tier !== "high") return "none";
const { actor } = ctx.actor;
if (actor === "human" || actor === "human_ai") return "reauth";
if (actor === "agent" && ctx.credential.type === "oauth") return "explicit_scope";
if (actor === "agent") return "user_confirmation";
return "reauth";
}Nota che un risultato Human non salta lo step-up. Seleziona lo step-up adatto a una persona interattiva. Le inferenze possono essere errate, quindi devono cambiare la forma del controllo, non rimuoverlo.
Step 5: indirizza alla revisione umana dove serve giudizio
Alcune azioni sono rare, ad alto impatto e difficili da automatizzare in modo sicuro: esportazioni massive dei dati dei clienti, recupero dell'account dopo la perdita delle credenziali o eliminazione di un'organizzazione. Per queste, una coda di revisione è spesso migliore di qualsiasi regola automatica.
Il contesto dell'actor rende la revisione più veloce e più equa perché fornisce al revisore le evidenze: la classe, la base, la credenziale usata, la cronologia di richieste simili. Il revisore decide. La classificazione informa.
function shouldReview(route: string, ctx: Context): boolean {
const reviewRoutes = new Set(["POST /exports", "POST /account/recover"]);
if (!reviewRoutes.has(route)) return false;
return !(ctx.credential.type === "oauth" && ctx.credential.scopes.includes("export:bulk"));
}Step 6: scegli il comportamento in caso di errore per livello
La valutazione dell'actor può andare in timeout o non avere evidenze. Decidi in anticipo cosa succede.
- Livello basso: fail open. La request procede normalmente.
- Livello medio: fail open, con l'autorizzazione normale in atto.
- Livello alto: fail closed sul controllo forte esistente. Le evidenze mancanti sull'actor non indeboliscono mai la protezione e non bloccano mai una request che altrimenti avrebbe superato i tuoi controlli normali.
La proprietà chiave è la simmetria. Un errore nella valutazione dell'actor dovrebbe lasciare l'azione esattamente sicura, e esattamente disponibile, com'era prima di aggiungere il contesto dell'actor.
Step 7: non prendere decisioni consequenziali sulle persone a partire da inferenze
Una classificazione inferred è un'evidenza su un'interazione, non un fatto su una persona. Non negare a qualcuno una domanda di lavoro, una decisione di credito, un risultato d'esame, un alloggio o l'accesso a un servizio essenziale perché una sessione sembrava automatizzata o assistita dall'AI. In questi flussi, usa il contesto dell'actor per scegliere un processo, come la revisione umana o un percorso di verifica alternativo, e assicurati che la persona possa completarlo.
Questo è sia un principio di equità sia un principio pratico. Le inferenze hanno falsi positivi e il costo di un falso positivo in questi flussi ricade su qualcuno che non ha fatto nulla di sbagliato.
Step 8: distribuisci prima in modalità osservazione
Prima che tutto questo influenzi le request reali, eseguilo in modalità observation e preview:
- Registra livello, classe dell'actor, base, tipo di credenziale e lo step-up richiesto dalla tua policy.
- Esamina campioni di request di livello alto per classe. Gli agent legittimi usano credenziali delegate o riutilizzano sessioni utente?
- Correggi le lacune che trovi, spesso offrendo credenziali con scope prima di passare all'enforce.
- Applica l'enforce a una piccola porzione di un singolo route, misura i tassi di completamento, poi amplia.
Step 9: mantieni un audit trail che risponda a "chi e per conto di chi"
Per ogni azione di livello alto, registra abbastanza da poter ricostruire in seguito cosa è successo:
- la classe dell'actor e la base al momento della request,
- il tipo di credenziale, il client a cui è stata emessa e il subject che rappresenta (OAuth 2.0 Token Exchange, RFC 8693, definisce un claim
actper registrare esattamente questo tipo di relazione "acting for"), - gli scope che hanno autorizzato l'azione,
- il requisito di step-up o revisione applicato e il suo esito.
audit.write({
action: "project.export",
actor: ctx.actor,
client: ctx.credential.type === "oauth" ? clientIdFromToken(req) : undefined,
subject: ctx.credential.subject,
scopes: ctx.credential.scopes,
requirement,
outcome: "approved",
at: new Date().toISOString(),
});Con questo tracciato, una domanda come "è stata una persona o un assistant a eliminare questo project e chi lo ha approvato" ha una risposta chiara. Questo rende l'incident response più rapida e dà agli utenti fiducia che l'aiuto automatizzato nella tua app sia responsabile.
Un esempio pratico
Un'app di project management vede un assistant aggiornare le scadenze dei task per gli utenti e, occasionalmente, tentare di esportare interi project.
- L'aggiornamento di un task è di livello medio. L'assistant usa un OAuth token con
tasks:write. Autorizzato, senza step-up. Consentito. - L'esportazione di un project è di livello alto. Il token dell'assistant non ha
export:bulk. L'app invia all'utente una request di conferma. L'utente approva e l'app concede lo scope per quel client. - Un client Unknown tenta la stessa esportazione con un cookie di sessione riutilizzato. Si applica il prompt di ri-autenticazione già esistente, esattamente come prima.
Nessuno è stato bloccato per essere un agent. Ogni azione ad alto impatto portava un'autorità esplicita.
Dove si colloca Proof of Actor
Proof of Actor fornisce il lato actor di questo modello: chi o che cosa sta agendo e su quale base. La tua application mantiene autenticazione, autorizzazione e decisione finale. Detect l'actor, Understand come ciascuna classe usa i tuoi route sensibili e Route ogni request al giusto livello di garanzia.
Per progettare questo per la tua application con il nostro team, richiedi accesso anticipato. Per scenari SaaS più dettagliati, vedi SaaS Apps & Websites.
Fonti
- RFC 6749: The OAuth 2.0 Authorization Framework: credenziali delegate con scope per i client
- RFC 9700: Best Current Practice for OAuth 2.0 Security: indicazioni di sicurezza attuali per le distribuzioni OAuth
- RFC 7009: OAuth 2.0 Token Revocation: revoca di una concessione senza cambiare le credenziali dell'utente
- RFC 9470: OAuth 2.0 Step Up Authentication Challenge Protocol: segnalazione che è richiesta un'autenticazione più forte o più recente
- RFC 8693: OAuth 2.0 Token Exchange: il claim
act(actor) per la delega - MCP specification 2026-07-28: Authorization: MCP server come resource server OAuth 2.1
- NIST SP 800-63B: Authentication and Authenticator Management: indicazioni sulla ri-autenticazione
- W3C Web Authentication (WebAuthn) Level 3: lo standard alla base dei prompt passkey
- RFC 9421: HTTP Message Signatures: ciò che stabilisce una firma di agent verificata