Inference Hooks: DLP inline per Claude Enterprise
- Cosa sono davvero gli Inference Hooks — un POST HTTPS da Anthropic verso un server che gestisci tu, non un WebSocket e non un agente on-device
- Lo schema del frame del prompt — cosa vede esattamente il tuo AI security server (e cosa non vede mai: system prompt, ragionamento nascosto, byte grezzi)
- Il JSON del verdetto: allow, deny con deny_reason, e perché deliberatamente oggi non esiste un'azione redact
- Il modello di firma — Standard Webhooks HMAC-SHA256, i due bug di verifica che colpiscono ogni prima integrazione, e il formato del segreto whsec_
- Le tre leve operative che decidono se gli utenti vengono bloccati o se il modello riceve traffico non ispezionato: timeout del verdetto, gestione dei fallimenti e circuit breaker
- Un playbook di rollout che non esplode il primo giorno — shadow mode → rollout percentuale → esclusioni per ruolo → enforce, in quest'ordine
Annunciati il 5 agosto 2026, gli Inference Hooks sono la prima risposta first-party di Anthropic alla domanda che ogni security team si pone dopo aver attivato una seat Claude Enterprise: come impedisco a un prompt con dati regolamentati di raggiungere mai il modello? La risposta, finora, era un proxy aziendale che intercettava il traffico TLS verso claude.ai — fragile, incompleto e cieco alla CLI Claude Code. Gli Inference Hooks spostano il punto di enforcement dentro il perimetro di Anthropic: per ogni prompt governato, Anthropic mette in pausa l'inference, invia in POST la transcript a un server gestito dalla tua organizzazione, e attende un allow o deny prima che il modello veda alcunché.
La versione in un paragrafo
La tua organizzazione espone un endpoint HTTPS. Anthropic gli invia ogni prompt governato come POST firmato (Standard Webhooks HMAC-SHA256). Il tuo server risponde {"action": "allow"} e l'inference procede, oppure {"action": "deny", "deny_reason": "..."} e l'utente vede il motivo senza mai arrivare al modello. L'endpoint copre chat Claude Enterprise, Claude Code e Cowork con una sola configurazione. Se il tuo server va in timeout o restituisce 500, la tua impostazione di failure handling decide se la richiesta blocca o procede non ispezionata. Fai rollout graduale con shadow mode + rollout percentuale + esclusioni per ruolo prima di attivare Enforce verdicts.
Inference Hooks vs Compliance API
Entrambe esistono per la stessa audience — team security, legal e compliance di Claude Enterprise — ma operano ai due estremi opposti del ciclo di vita della richiesta.
| Inference Hooks | Compliance API | |
|---|---|---|
| Quando | Inline, prima che l'inference parta | A posteriori |
| Cosa fa | Autorizza o nega ogni richiesta governata in tempo reale | Recupera attività, chat, file, progetti, utenti per audit ed export |
| Direzione | Anthropic → il tuo server | Tu → Anthropic |
| Usalo per | Fermare una fuga | Provare cosa è successo |
La maggior parte delle enterprise userà entrambi. Gli hook sono il filo teso; la Compliance API è il log di audit.
Come funziona il round trip del verdetto
- Cioè chat claude.ai, Claude Code (web, desktop, CLI) o Claude Cowork. Le richieste accessorie come la generazione del titolo della conversazione NON vengono inviate. La modalità vocale è fuori scope per la beta.
- Un POST HTTPS all'URL configurato dal tuo admin. Gli header includono Content-Type: application/json, User-Agent: anthropic-dlp/1 e i tre header di firma Standard Webhooks (webhook-id, webhook-timestamp, webhook-signature).
- Calcola HMAC-SHA256 su `{webhook-id}.{webhook-timestamp}.{byte grezzi del body}` con il segreto whsec_ decodificato in base64. Rifiuta qualunque cosa più vecchia di 5 minuti dal tuo orologio o senza una firma corrispondente.
- O {"action": "allow"} o {"action": "deny", "deny_reason": "..."}. Anthropic legge al massimo 64 KiB dal body della risposta e NON segue i redirect.
- Su allow, l'inference procede normalmente. Su deny, l'utente vede il tuo deny_reason seguito dal messaggio permanente configurato dal tuo admin; il modello non vede mai il prompt. Ogni negazione viene registrata nell'Activity Feed dell'organizzazione.
Il senso di far girare tutto sui server di Anthropic, non sui device degli utenti, è l'uniformità: una config, un server, e ogni richiesta governata su ogni superficie viene ispezionata allo stesso modo. Non c'è nulla da installare sui laptop dei dipendenti, e non c'è alcuna integrazione per-app da tenere sincronizzata.
Il frame del prompt
Ogni richiesta è un body JSON con questi campi top-level:
| Campo | Tipo | Descrizione |
|---|---|---|
type | string | Oggi sempre "prompt". Appariranno nuovi tipi di evento — restituisci allow su valori non riconosciuti così non attivi il circuit breaker. |
request_id | string | Identificatore opaco per chiamata di inference. Coincide con l'header webhook-id — usalo come chiave di idempotenza. |
tenant_id | string | null | Identificatore opaco per l'organizzazione. |
actor | object | Discriminato su type ("user" è l'unico valore oggi). Porta un id taggato stabile tra le richieste di un utente e email_address quando disponibile. Entrambi i campi possono essere null. |
source | object | {"application": "..."}. Valori noti: claude-ai, claude-code, config-test (usato dal pulsante admin "Test connection"). Enum aperto — appariranno nuovi valori. |
session_id | string | null | Identificatore opaco della conversazione. Non parsarlo. Best-effort per Claude Code. |
model | string | null | Identificatore pubblico del modello per questa richiesta, quando disponibile. |
messages | array | La transcript della conversazione fino al punto di inference — vedi Blocchi di contenuto. |
metadata | object | Mappa di estensione riservata. Oggi vuota. Tollera chiavi che non conosci. |
Una richiesta live minima ha questo aspetto:
{
"type": "prompt",
"request_id": "req_abc123",
"tenant_id": "11111111-1111-1111-1111-111111111111",
"actor": {
"type": "user",
"id": "user_01AbCdEfGhIjKlMnOpQrStUv",
"email_address": "alice@example.com"
},
"source": { "application": "claude-ai" },
"session_id": "22222222-2222-2222-2222-222222222222",
"model": "claude-sonnet-5",
"messages": [
{
"role": "user",
"content": [
{ "type": "text", "text": "Summarize the attached report." },
{
"type": "attachment",
"file_name": "q2-report.pdf",
"media_type": "application/pdf",
"size_bytes": 48213,
"text": "Q2 revenue grew 14% quarter over quarter..."
}
]
}
],
"metadata": {}
}
Blocchi di contenuto
Ogni entry messages[].content[] ha un type e segue il modello dei content block dell'API Messages pubblica. I risultati dei tool appaiono sotto il ruolo user.
type del blocco | Campi |
|---|---|
text | text |
tool_use | id, tool_name, input |
tool_result | content (testo, unito con newline; le parti binarie sono marker segnaposto), is_error, tool_name, tool_use_id |
attachment | file_name, media_type, size_bytes, text (testo estratto, transcript o metadati del link) |
Cosa non contiene mai la transcript
È la parte che fa inciampare i privacy review.
- Nessun system prompt. Né quelli di Anthropic, né i tuoi (via projects/skills), né la costituzione del modello — niente di tutto ciò viene inviato.
- Nessun ragionamento nascosto. La catena di extended-thinking di Claude non fa parte della transcript che il tuo server vede.
- Nessuna definizione di tool. Solo le chiamate e i loro risultati.
- Nessun byte grezzo. File e immagini sono rappresentati da metadati e testo estratto. Il contenuto solo-immagine (uno screenshot di un documento) non verrà ispezionato.
- Nessun contesto interno di Anthropic o trust boundary.
La transcript è la conversazione come la vede l'utente finale, più le tracce dei tool. Un blocco o turno i cui contenuti sono tutti esclusi viene rimosso interamente, quindi non dare per scontata la stretta alternanza user/assistant quando parsi.
Un gotcha sulla size
Le transcript vengono inviate non troncate fino a un tetto di 10 MB. I default comuni sono molto più piccoli — nginx client_max_body_size è 1 MB, Express express.json() è 100 kB, la maggior parte dei reverse proxy PaaS si ferma a pochi MB. Un body che il tuo server rifiuta è un webhook failure, che sotto la failure handling Allow the request significa che il prompt oversize raggiunge il modello non ispezionato. Alza i limiti del body prima di fare enforce.
Lo schema del verdetto
Rispondi con HTTP 200 per entrambi gli esiti. Il campo action fa da discriminante.
Allow:
{ "action": "allow" }
Deny:
{
"action": "deny",
"deny_reason": "This prompt appears to contain customer payment card data, which your organization's policy does not allow.",
"reference_id": "scan_01HXPT4R9V"
}
| Campo | Tipo & limite | Semantica |
|---|---|---|
action | "allow" o "deny"; obbligatorio | allow lascia procedere l'inference. deny rifiuta la richiesta. |
deny_reason | string o null; al massimo 500 caratteri, valori più lunghi vengono troncati | Mostrato all'utente finale, in coda al messaggio permanente configurato dal tuo admin. Scrivilo per l'utente — digli cosa cambiare, non come si chiamava la regola del tuo scanner. |
reference_id | string o null; al massimo 50 caratteri da [A-Za-z0-9._:/-] | Il tuo identificatore per questa valutazione. Registrato sull'entry inference_hooks_request_denied della negazione nell'Activity Feed, mai mostrato all'utente finale. Tienilo opaco — niente contenuto della richiesta, niente dati personali. |
Perché non c'è un'azione redact
Il verdetto è deliberatamente binario. Anthropic avrebbe potuto aggiungere {"action": "redact", "rewritten_prompt": "..."} e lasciare che il tuo server DLP sanitizzasse la transcript in transito — ma questo significherebbe che Anthropic invia al modello qualunque cosa restituisca la tua scatola, sotto l'autorità della tua organizzazione. Il design mantiene netto quel trust boundary: il tuo server valuta i contenuti, non li scrive. Se hai bisogno di redazione, falla nel client prima che l'utente prema invio.
Un deny non viene mai scartato per problemi di formato
Un deny_reason oversize viene troncato; un reference_id malformato viene silenziosamente scartato; l'action resta comunque onorata. Il contrario non vale: qualunque cosa diversa da HTTP 200 con un verdetto parsabile è un webhook failure, non un deny. Se segnali i blocchi con HTTP 403 i tuoi deny si trasformano silenziosamente in allow fail-open (o blocchi, a seconda della failure handling) e ognuno di essi conta contro il circuit breaker.
Il server funzionante più piccolo
AI security server allow-all in 12 righe di Python
# Run with: python server.py — expose on an https:// URL your admin configures.
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
class VerdictHandler(BaseHTTPRequestHandler):
protocol_version = "HTTP/1.1" # keep the connection open between verdicts
def do_POST(self):
self.rfile.read(int(self.headers.get("Content-Length", 0)))
verdict = b'{"action": "allow"}'
self.send_response(200)
self.send_header("Content-Type", "application/json")
self.send_header("Content-Length", str(len(verdict)))
self.end_headers()
self.wfile.write(verdict)
ThreadingHTTPServer(("", 8000), VerdictHandler).serve_forever()Mettilo dietro un reverse proxy TLS-terminating sulla porta 443, configuralo come tuo endpoint, premi Test connection nella console admin — vedrai il verdetto allow. Questa è esattamente la forma di un'integrazione archival-only: restituisci allow incondizionatamente e persisti il frame dopo aver risposto, come alternativa push al polling della Compliance API. Non è una forma con cui dovresti fare enforcement — accetta ogni richiesta, incluse quelle non firmate. Aggiungi la verifica della firma prima di attivare Enforce verdicts.
Verifica della firma
La firma segue la spec Standard Webhooks. Tre header, lowercase come li invia Anthropic ma case-insensitive in lookup (i proxy ricambiano il case).
| Header | Contenuto |
|---|---|
webhook-id | Univoco per delivery. Coincide con il request_id del body. Usalo come chiave di idempotenza. |
webhook-timestamp | Unix time in secondi, come stringa decimale. Rifiuta se più di 5 minuti dal tuo orologio in una qualsiasi direzione — quella è la replay window. |
webhook-signature | Valori v1,<base64> separati da spazio. Ognuno è un HMAC-SHA256 sulla stringa di byte {webhook-id}.{webhook-timestamp}.{byte grezzi del body}. Accetta la richiesta se un qualunque valore matcha il tuo — usa confronto a tempo costante. |
I due bug che colpiscono ogni prima integrazione
- Verifica i byte grezzi, NON JSON ri-serializzato. Calcola l'HMAC sul body esattamente come ricevuto, prima di qualunque parsing o riserializzazione. Un round trip json.loads() → json.dumps() cambia i whitespace e muore qui.
- Decodifica il segreto con un decoder base64 STANDARD, non URL-safe. Il signing secret è il valore dopo il prefisso whsec_, codificato con l'alfabeto standard (+ e /). Un decoder URL-safe deriva i byte di chiave sbagliati ogni volta che il segreto contiene + o /, ovvero quasi sempre — e il fallimento è un mismatch silenzioso a tempo costante.
Implementazione Python di riferimento (compressa dai docs Anthropic):
import base64, hashlib, hmac, time
TOLERANCE_SECONDS = 300
def verify(secret: str, headers: dict[str, str], body: bytes) -> bool:
h = {k.lower(): v for k, v in headers.items()}
try:
msg_id, ts, sigs = h["webhook-id"], h["webhook-timestamp"], h["webhook-signature"]
except KeyError:
return False # unsigned, not from Anthropic
try:
signed_at = int(ts)
except ValueError:
return False
if abs(time.time() - signed_at) > TOLERANCE_SECONDS:
return False # replayed, or clocks disagree
try:
key = base64.b64decode(secret.removeprefix("whsec_"), validate=True)
except ValueError:
return False # misconfigured secret
payload = f"{msg_id}.{ts}.".encode() + body
expected = b"v1," + base64.b64encode(hmac.new(key, payload, hashlib.sha256).digest())
return any(hmac.compare_digest(expected, s.encode()) for s in sigs.split())
Rotazione del segreto
La rotazione è un cutover immediato lato admin, ma le richieste firmate con il segreto precedente possono ancora arrivare per circa un minuto dopo, oltre a tutto ciò che è già in transito. Fa' in modo che il tuo server accetti firme sia dal vecchio che dal nuovo segreto durante la finestra di rotazione così quegli stragglers non vengano rifiutati come non firmati.
Eccezione una tantum
Un test di connessione inviato prima del primo salvataggio della tua organizzazione arriva non firmato, perché il signing secret ancora non esiste. Accetta le richieste non firmate finché il tuo admin non conferma che il segreto esiste, poi rifiutale.
Semantica operativa
Timeout
Il tuo admin imposta un timeout del verdetto tra 1 e 10.000 ms, con default a 5.000 ms. Quel budget copre l'intero round trip: connessione, handshake TLS, upload del body della richiesta, download del body della risposta.
Retry
Anthropic ritenta esattamente una volta, dopo un ritardo di 100 ms, e solo quando il tentativo di connessione fallisce. Non su 500. Non su timeout. Non su errori di parse. Una volta che il tuo server ha risposto — con qualunque cosa — lo scambio è chiuso. Il retry condivide lo stesso budget di timeout e porta lo stesso webhook-id e la stessa firma, quindi è sicuro chiavare la deduplica su webhook-id.
Gestione dei fallimenti
Tutto il resto che non è un 200 pulito con verdetto è un webhook failure: timeout, status non-200 (redirect inclusi), body di risposta non parsabili o oversize, endpoint irraggiungibili. Al fallimento, decide l'impostazione della tua organizzazione:
- Block the request. Default sicuro per ambienti ad alta regolazione. Se il tuo server DLP è giù, gli utenti sono bloccati. La disponibilità di Claude diventa la disponibilità del tuo scanner.
- Allow the request. Gli utenti continuano a lavorare mentre il tuo server si riprende. I prompt fluiscono non ispezionati durante il disservizio — un tradeoff accettato da molte org, ma pianifica come riconciliare il gap nel tuo audit trail.
Circuit breaker
Fallimenti di webhook sostenuti attribuibili al tuo AI security server fanno scattare un circuit breaker che ferma l'enforcement: Anthropic smette di chiamare il tuo server, e la failure handling si applica a ogni richiesta. Il recovery non è automatico — sistema il server, poi fa' che il tuo admin riattivi Enforce verdicts. In pratica questo significa: un type top-level sconosciuto dovrebbe restituire {"action": "allow"}, non un HTTP 500 — altrimenti un futuro nuovo tipo di evento ti farebbe finire in territorio circuit-breaker il giorno del rollout.
Latenza
Ogni richiesta governata nella tua organizzazione paga il round-trip del tuo AI security server in latenza aggiunta. Fai load-test prima di rilasciare su un'org grande; uno scanner da 4 secondi è invisibile su un prompt di chat ma un incubo sui loop di tool di Claude Code che fanno molte richieste di fila.
Allowlist degli IP sorgente
Le richieste originano da 160.79.106.0/24, parte degli outbound IP range pubblicati da Anthropic. Metti in allowlist quel blocco, non i range inbound sulla stessa pagina — sono liste diverse. E l'allowlisting non sostituisce la verifica della firma: il blocco trasporta traffico egress di Anthropic oltre agli Inference Hooks.
Il playbook di rollout
- La prima cosa che attivi. Il tuo server valuta ogni richiesta ma nessun deny viene applicato. Puoi tarare le tue regole contro una settimana di traffico reale prima che un singolo utente venga bloccato.
- Quando attivi l'enforcement, parti (per esempio) dal 10% e sali. Riduce il blast radius se il tuo scanner ha uno spike di falsi positivi.
- I ruoli che sai bruceranno prompt più velocemente di quanto il tuo scanner riesca a stare al passo (senior engineering, SRE on-call) possono essere esentati mentre tari. Non per sempre — ma utile durante la rampa.
- Solo dopo che i tre precedenti hanno girato puliti per un periodo di soak che imposti tu. Questo è quando le tue stringhe deny_reason cominciano finalmente a raggiungere gli utenti, quindi rivedile ancora una volta prima di girare l'interruttore.
I docs Anthropic lo dicono chiaramente: bloccare i dipendenti il primo giorno è il modo in cui i programmi DLP muoiono. Lo shadow mode esiste per un motivo.
Progetta la tua integrazione
- Fai deduplica su webhook-id. È univoco per delivery e matcha request_id nel body. Un retry da connection-failure lo riusa, quindi è una chiave di idempotenza pulita.
- Salva ogni verdetto con il suo reference_id. Anthropic registra reference_id sull'entry dell'Activity Feed di ogni negazione, così puoi ricollegare le negazioni all'esatta decisione di scan nel tuo sistema.
- Per integrazioni archival always-allow, RISPONDI prima, poi persisti. Rispondere prima di scrivere tiene il tuo round trip fuori dal critical path dell'utente — il tuo sistema di storage non è sul hot path.
- Scrivi deny_reason per la persona, non per il SIEM. 'Rimuovi i numeri di carta di credito dal tuo prompt e invia di nuovo' batte 'PCI_REGEX_2A tripped, reference 4471'. Gli utenti agiranno sul primo.
Matrice di copertura
| Superficie / accesso | Ispezionata dagli Inference Hooks? |
|---|---|
| claude.ai (web, desktop, mobile) | ✅ Sì |
| Claude Code (web, desktop, CLI) | ✅ Sì (session_id è best-effort, dichiarato dal client) |
| Claude Cowork | ✅ Sì |
| Modalità vocale | ❌ Non nella beta |
| Generazione titolo conversazione, altri accessori | ❌ Non inviati |
| System prompt, definizioni di tool | ❌ Mai inviati |
| Byte grezzi di file / immagini | ❌ Mai inviati (il testo estratto sì) |
| Contenuto solo-immagine (es. screenshot di doc) | ❌ Non ispezionato |
| API key di Claude Platform (accesso developer) | ❌ Fuori scope degli Inference Hooks (org Platform, non Enterprise) |
| Deploy su Amazon Bedrock / Google Cloud | ❌ Non disponibile su quei piani |
Errori comuni
- Segnalare un blocco con HTTP 403. È un webhook failure, non un deny — il tuo verdetto di policy viene buttato via e la failure handling prende il sopravvento.
- Restituire qualunque action diversa da 'allow' o 'deny'. Stessa storia: webhook failure. Se sei tentato di aggiungere un terzo stato, fallo nel tuo audit log, non nel verdetto.
- Limiti default piccoli sul body (Express 100 kB, nginx 1 MB). Una transcript da 3 MB con il testo estratto di un PDF grosso farà 413 sul tuo reverse proxy. Alza i limiti per accogliere il tetto da 10 MB.
- Decodifica base64 URL-safe del segreto whsec_. Mismatch silenzioso a tempo costante su ogni richiesta finché non ti accorgi che tutte le tue richieste sono 'non firmate'.
- Ri-serializzare il body prima dell'HMAC. Verifica i byte grezzi esattamente come ricevuti. json.loads + json.dumps cambia i whitespace e rompe la firma.
- Rifiutare `type` top-level sconosciuti con 500. Fa scattare il circuit breaker il giorno in cui Anthropic rilascia un nuovo tipo di evento. Restituisci `allow` sui type sconosciuti.
- Rifiutare valori di `source.application` che non riconosci. È un enum aperto. Nuovi valori appariranno e le vecchie integrazioni non devono blocarsi su di essi.
- Presupporre l'alternanza user/assistant. I turni i cui blocchi sono tutti esclusi vengono rimossi da `messages`. Parsa in modo difensivo.
Quando usare Inference Hooks vs un proxy client-side
Alcune org girano ancora proxy TLS-intercepting per coprire tutto ciò che i loro dipendenti fanno online. Gli Inference Hooks non sostituiscono un proxy — sono un punto di enforcement specifico per Claude che sta dentro il perimetro di Anthropic e vede una vista strutturata più ricca della conversazione di quanto veda un proxy che sulla wire osserva solo byte cifrati.
- Usa Inference Hooks quando vuoi accesso strutturato a ciò che il modello vedrà davvero (tool call, allegati, transcript), copertura uniforme su chat + Code + Cowork, e nessun install per-device.
- Tieni il tuo network DLP per tutto il resto sulla box: upload di file verso servizi non-Claude, traffico browser fuori da claude.ai, allegati email. I due non si sovrappongono.
- Aggiungi la Compliance API per audit ed export a posteriori.
Quiz
Check yourself
0/3Prossimo
- Admin API: automatizza la tua org Claude — endpoint di gestione utenti e RBAC Enterprise che si sposano naturalmente con gli hook.
- MCP 2026-07-28: la spec stateless — il lato tool-call di ciò che i tuoi hook vedranno nei blocchi
tool_useetool_result. - Rifiuti e sicurezza — i segnali di rifiuto in-model di Claude, che scattano dopo che il tuo hook ha lasciato passare un prompt.
- Modelli e prezzi correnti