Salta al contenuto principale
GUIDA

Come aggiungere il contesto dell'attore a un'app Node.js

Una guida passo per passo per costruire un middleware che registra chi o cosa c'è dietro ogni request, così puoi studiare il traffico reale prima di imporre qualsiasi cosa.

Toshendra Sharma

Di Toshendra SharmaFondatore e CEO, AgentCredia · Updated November 13, 2026

Principiante · 9 min di lettura

Perché iniziare con il contesto dell'attore invece che con le regole?

La maggior parte dei team che nota agenti AI nel proprio traffico corre subito verso una regola. Blocca un user agent, aggiungi una challenge, stringi un rate limit. Il problema è che una regola scritta prima di aver compreso il traffico tende a colpire le persone sbagliate. Gli assistenti legittimi vengono bloccati, gli utenti reali vengono sottoposti a challenge e l'automazione di cui ti preoccupavi continua a passare perché non si è mai identificata in primo luogo.

Il contesto dell'attore è il passo precedente alle regole. Per ogni request, registri ciò che la tua application può osservare su chi o cosa sta agendo: una persona interattiva, una persona che lavora con un assistente AI, un agent autonomo oppure qualcosa che non riesci ancora a distinguere. Registri l'evidenza dietro questa lettura e la mantieni accanto all'esito della request. Dopo una o due settimane di questo, hai qualcosa che nessun modello di regola può darti: una mappa del tuo traffico. Se stai ancora decidendo se gli agent arrivano davvero sul tuo sito, inizia da come capire se il traffico del sito web proviene da agent AI.

Questa guida costruisce quel livello in un'applicazione Node.js usando middleware semplice. Funziona con Express e le stesse idee si applicano anche a Fastify, Koa, Hono o a un route handler di Next.js.

Cosa costruirai

Alla fine avrai:

  1. Un middleware che estrae l'evidenza dell'attore da ogni request.
  2. Una classificazione piccola e esplicita con una base dichiarata per ogni risultato.
  3. Log di osservazione strutturati che non cambiano mai il modo in cui la request viene gestita.
  4. Un modo per allegare il risultato alla request, così i handler successivi possono leggerlo.

Nulla qui blocca una request. È voluto.

Passo 1: Quale evidenza può vedere davvero il tuo server?

Un server vede meno di quanto molti pensino. Prima di scrivere codice, elenca i segnali disponibili al tuo edge:

  • Firme della request. Alcuni client automatizzati firmano le request usando HTTP Message Signatures (RFC 9421), inviando gli header Signature-Input e Signature, spesso insieme a un header Signature-Agent che punta alle chiavi pubblicate dall'operatore, come descritto nel draft del protocollo IETF Web Bot Auth.
  • Identità dichiarata. I client possono nominarsi nell'header User-Agent, in un header From, oppure tramite metadati di protocollo come un nome client MCP. I client MCP riportano un nome clientInfo scelto autonomamente nella request di initialize della revisione 2025-11-25, e la revisione 2026-07-28 trasporta l'identità del client come metadati per request. Queste sono dichiarazioni e chiunque può copiarle.
  • Contesto di autenticazione. Se la request include un cookie di sessione, un bearer token o una API key, e di quale tipo di credenziale si tratta.
  • Forma del protocollo. Header Accept, se il client richiede HTML o JSON, se carica asset della pagina e come gestisce redirect e cookie.
  • Dichiarazioni dell'applicazione. Il tuo prodotto può conoscere cose che la rete non può vedere, come un utente che sceglie un'opzione "AI assisted" o una request che arriva dalla tua funzionalità di assistente integrato.

Ogni segnale ha una forza diversa. Una firma validata è verificabile. Una stringa user agent è una dichiarazione. Un cookie mancante è solo mancante. Tenere separati questi elementi è il punto centrale dell'esercizio.

Passo 2: Modella esplicitamente il risultato

Definisci la forma di un risultato dell'attore prima di scrivere la logica di rilevamento. Un modello piccolo e tipizzato aiuta tutti a restare onesti su cosa significhi una classificazione. Le quattro classi usate qui sono spiegate in Human, AI-assisted or agent: a practical taxonomy for application traffic.

TS
export type ActorClass = "human" | "human_ai" | "agent" | "unknown";
export type EvidenceBasis = "verified" | "declared" | "inferred" | "unknown";

export interface ActorEvidence {
  signaturePresent: boolean;
  signatureAgent?: string;
  declaredClient?: string;
  credentialType: "session" | "api_key" | "bearer" | "none";
  acceptsHtml: boolean;
  appDeclaredAssist: boolean;
}

export interface ActorResult {
  actor: ActorClass;
  basis: EvidenceBasis;
  reasons: string[];
  evidence: ActorEvidence;
  observedAt: string;
}

Due campi contano più degli altri. actor è la classe. basis indica perché ci credi. Un risultato agent basato su una firma validata e un risultato agent basato su una stringa user agent sono entrambi "agent", ma meritano un trattamento molto diverso in seguito, quindi non vanno mai fusi.

Passo 3: Estrai l'evidenza nel middleware

La fase di estrazione legge gli header e lo stato della request. Non decide ancora nulla.

TS
import type { Request, Response, NextFunction } from "express";

const KNOWN_AGENT_TOKENS = ["bot", "crawler", "spider", "agent", "gpt", "claude"];

export function extractEvidence(req: Request): ActorEvidence {
  const ua = (req.get("user-agent") || "").toLowerCase();
  const auth = req.get("authorization") || "";
  const hasSession = Boolean(req.cookies?.session);

  let credentialType: ActorEvidence["credentialType"] = "none";
  if (hasSession) credentialType = "session";
  else if (auth.toLowerCase().startsWith("bearer ")) credentialType = "bearer";
  else if (req.get("x-api-key")) credentialType = "api_key";

  const declaredToken = KNOWN_AGENT_TOKENS.find((t) => ua.includes(t));

  return {
    signaturePresent: Boolean(req.get("signature") && req.get("signature-input")),
    signatureAgent: req.get("signature-agent") || undefined,
    declaredClient: declaredToken ? ua.slice(0, 200) : undefined,
    credentialType,
    acceptsHtml: (req.get("accept") || "").includes("text/html"),
    appDeclaredAssist: req.get("x-app-assist") === "1",
  };
}

Alcune note su questo codice:

  • signaturePresent registra solo che esistono header di firma. La validazione è un passaggio separato, trattato nella guida sulla verifica delle request agent firmate.
  • L'elenco dei token user agent è volutamente semplice. Produce una dichiarazione, mai una verifica.
  • x-app-assist sta al posto di qualunque meccanismo il tuo frontend invii quando un utente passa attraverso la tua funzionalità AI integrata. Sostituiscilo con il tuo meccanismo reale.

Passo 4: Classifica con una base dichiarata

Ora trasforma l'evidenza in un risultato. Mantieni le regole brevi, ordinate dalla prova più forte alla più debole, e arriva sempre fino a Unknown.

TS
export async function classify(
  evidence: ActorEvidence,
  verifySignature: () => Promise<boolean>,
): Promise<ActorResult> {
  const reasons: string[] = [];
  const observedAt = new Date().toISOString();

  if (evidence.signaturePresent && (await verifySignature())) {
    reasons.push("request signature validated");
    return { actor: "agent", basis: "verified", reasons, evidence, observedAt };
  }

  if (evidence.appDeclaredAssist) {
    reasons.push("application reported its built-in assistant");
    return { actor: "human_ai", basis: "declared", reasons, evidence, observedAt };
  }

  if (evidence.declaredClient) {
    reasons.push("client declared itself as automated");
    return { actor: "agent", basis: "declared", reasons, evidence, observedAt };
  }

  if (evidence.credentialType === "session" && evidence.acceptsHtml) {
    reasons.push("interactive browser session");
    return { actor: "human", basis: "inferred", reasons, evidence, observedAt };
  }

  reasons.push("insufficient evidence");
  return { actor: "unknown", basis: "unknown", reasons, evidence, observedAt };
}

Nota cosa non fa questo codice. Non promuove mai una dichiarazione a una verifica. Non assegna mai Human a una request che non ha alcun segnale interattivo. Quando non riesce a capire, dice Unknown. Unknown è una risposta valida e utile, e ti dice esattamente dove finisce la tua visibilità. Presentiamo il caso completo in perché Unknown è un risultato valido per il traffico AI.

Passo 5: Integralo in modalità di osservazione

Monta il middleware all'inizio, allega il risultato alla request e registralo. Non fare branching su quel risultato.

TS
import express from "express";
import cookieParser from "cookie-parser";

const app = express();
app.use(cookieParser());

app.use(async (req, res, next) => {
  try {
    const evidence = extractEvidence(req);
    const result = await classify(evidence, async () => false);
    res.locals.actor = result;

    res.on("finish", () => {
      logActor({
        route: req.route?.path ?? req.path,
        method: req.method,
        status: res.statusCode,
        actor: result.actor,
        basis: result.basis,
        reasons: result.reasons,
      });
    });
  } catch {
    res.locals.actor = undefined;
  }
  next();
});

Tre dettagli rendono tutto questo sicuro:

  1. Il blocco try garantisce che un errore nel rilevamento non influenzi mai la response. L'osservazione deve fallire in modo aperto.
  2. La registrazione avviene su finish, così catturi il vero status code e puoi confrontare in seguito classificazioni ed esiti.
  3. verifySignature è lasciato come stub su false finché non implementi una validazione reale. In questo modo le request firmate restano nei bucket declared o unknown invece di essere sovrastimate.

Passo 6: Registra per l'analisi, non solo per il debug

Scrivi log che puoi aggregare. Una riga strutturata per request è sufficiente.

TS
function logActor(entry: Record<string, unknown>) {
  process.stdout.write(JSON.stringify({ type: "actor_observation", ...entry }) + "\n");
}

Tieni i dati personali fuori da questi log. Ti servono il route, la classe, la base, i motivi e l'esito. Non ti servono indirizzi IP completi, cookie grezzi o body delle request per capire la composizione del traffico. Se conservi un identificatore per correlazione, hashalo con un sale rotante e imposta un periodo di retention breve.

Passo 7: Leggi i dati prima di agire

Esegui la modalità di osservazione abbastanza a lungo da vedere i normali schemi settimanali. Poi rispondi ad alcune domande:

  • Quali route attirano gli agent? Di solito documentazione, catalogo e endpoint di ricerca sì. I flussi account e checkout di solito no.
  • Quale quota dei risultati agent è verificata rispetto a dichiarata? Quel rapporto ti dice quanto puoi fare affidamento sull'identità quando scrivi le policy.
  • Dove si concentra Unknown? Un tasso alto di Unknown su una route spesso significa che lì manca un segnale, per esempio una pagina che non esegue mai il tuo frontend.
  • Gli esiti differiscono per classe? Confronta tassi di errore, passaggi di conversione o segnalazioni di abuso tra le classi. Da qui nascono le vere idee di policy.

Le risposte spesso sorprendono. I team scoprono regolarmente che una gran parte del loro "bot problem" è in realtà automazione legittima che userebbe volentieri un endpoint strutturato, se esistesse.

Passo 8: Esporre il risultato al resto della tua app

Una volta che i dati scorrono, lascia che i handler leggano res.locals.actor per usi non di enforcement: tag di analytics, scelta del formato di risposta o registrazione del fatto che un ticket di supporto proviene da una sessione AI-assisted. Questi usi migliorano l'esperienza senza rischiare che qualcuno venga escluso.

TS
app.get("/docs/:page", (req, res) => {
  const actor = res.locals.actor as ActorResult | undefined;
  if (actor?.actor === "agent" && req.accepts("text/markdown")) {
    return res.type("text/markdown").send(renderMarkdown(req.params.page));
  }
  return res.render("doc", { page: req.params.page });
});

L'enforcement, come challenge, rate limit o controlli step-up, arriva più tardi, dopo aver misurato i falsi positivi. La guida sul rilascio di policy consapevoli dell'attore descrive quel processo.

Quali errori dovresti evitare?

  • Trattare lo user agent come identità. È l'header più facile da cambiare. Registralo come dichiarazione. Anche gli operatori di AI lo dicono: la guida di Anthropic per il suo browser agent ospitato nota che chiunque può inviare lo stesso header User-Agent e raccomanda di controllare i suoi indirizzi IP pubblicati prima di trattare il traffico come autentico.
  • Partire sempre da Human. Se il tuo codice dice Human ogni volta che non trova segnali di bot, i tuoi numeri Human saranno gonfiati e le tue policy si fideranno del traffico sbagliato.
  • Mescolare classe e base. Conserva sempre entrambi. Un dashboard che mostra solo "Agent" nasconde la differenza tra un operatore verificato e una stringa copiata.
  • Bloccare durante l'osservazione. Anche un blocco temporaneo invalida le misurazioni, perché i client bloccati cambiano comportamento.

Dove si inserisce Proof of Actor

Il middleware sopra è un buon punto di partenza e mostra il modello centrale: prima l'evidenza, una base esplicita per ogni risultato, Unknown mantenuto visibile e le decisioni lasciate alla tua application. Proof of Actor si basa sullo stesso modello con una copertura di segnali più ampia tra server, edge, browser e contesti MCP, e con analytics che ti permettono di Detect, Understand e Route con fiducia.

Se vuoi aiuto per portare questo approccio su un'app in produzione, candida il tuo team all'accesso anticipato. Per vedere il flusso decisionale completo, leggi Come funziona Proof of Actor.

Fonti

Costruiscilo con noi.

Unisciti al founding developer program di Proof of Actor.

Preferisci parlare prima? Parla con i fondatori