Ir para o conteúdo principal
GUIA

Como adicionar contexto de ator a um app Node.js

Um guia passo a passo para criar middleware que registra quem ou o que está por trás de cada requisição, para que você possa estudar o tráfego real antes de impor qualquer regra.

Toshendra Sharma

Por Toshendra SharmaFundador e CEO, AgentCredia · Updated November 13, 2026

Iniciante · 9 min de leitura

Por que começar com contexto de ator em vez de regras?

A maioria das equipes que percebe agentes de IA no tráfego pensa primeiro em uma regra. Bloquear um user agent, adicionar um desafio, apertar um rate limit. O problema é que uma regra escrita antes de você entender o tráfego tende a atingir as pessoas erradas. Assistentes legítimos são bloqueados, usuários reais são desafiados e a automação que você realmente temia continua chegando porque nunca se identificou desde o início.

Contexto de ator é a etapa anterior às regras. Para cada requisição, você registra o que sua aplicação consegue observar sobre quem ou o que está agindo: uma pessoa interagindo, uma pessoa trabalhando com um assistente de IA, um agente autônomo ou algo que ainda não dá para distinguir. Você registra a evidência por trás dessa visão e a mantém ao lado do resultado da requisição. Depois de uma ou duas semanas disso, você tem algo que nenhum modelo de regra entrega: um mapa do seu próprio tráfego. Se você ainda está decidindo se agentes chegam ao seu site, comece com como identificar se o tráfego do site vem de agentes de IA.

Este guia constrói essa camada em uma aplicação Node.js usando middleware simples. Funciona com Express, e as mesmas ideias se aplicam a Fastify, Koa, Hono ou a um route handler do Next.js.

O que você vai construir

Ao final, você terá:

  1. Um middleware que extrai evidências de ator de cada requisição.
  2. Uma classificação pequena e explícita, com uma base declarada para cada resultado.
  3. Logs estruturados de observação que nunca mudam a forma como a requisição é tratada.
  4. Um modo de anexar o resultado à requisição para que handlers posteriores possam lê-lo.

Nada aqui bloqueia uma requisição. Isso é intencional.

Etapa 1: Que evidências seu servidor realmente consegue ver?

Um servidor vê menos do que as pessoas imaginam. Antes de escrever código, liste os sinais disponíveis na sua borda:

  • Assinaturas de requisição. Alguns clientes automatizados assinam requisições usando HTTP Message Signatures (RFC 9421), enviando headers Signature-Input e Signature, muitas vezes junto com um header Signature-Agent que aponta para as chaves publicadas do operador, conforme descrito no rascunho do protocolo Web Bot Auth do IETF Web Bot Auth protocol draft.
  • Identidade declarada. Clientes podem se nomear no header User-Agent, em um header From ou por meio de metadados de protocolo, como um nome de cliente MCP. Clientes MCP informam um nome clientInfo escolhido por eles no initialize request da revisão de 2025-11-25, e a revisão de 2026-07-28 carrega a identidade do cliente como per-request metadata. Essas são declarações, e qualquer pessoa pode copiá-las.
  • Contexto de autenticação. Se a requisição carrega um cookie de sessão, um bearer token ou uma API key, e que tipo de credencial ela é.
  • Forma do protocolo. Headers Accept, se o cliente solicita HTML ou JSON, se carrega assets da página e como lida com redirects e cookies.
  • Declarações da aplicação. Seu próprio produto pode saber coisas que a rede não sabe, como um usuário escolhendo uma opção "AI assisted" ou uma requisição vinda do recurso de assistente embutido.

Cada sinal tem uma força diferente. Uma assinatura validada pode ser verificada. Uma string de user agent é uma declaração. Um cookie ausente é apenas um cookie ausente. Manter esses elementos separados é o objetivo central do exercício.

Etapa 2: Modele o resultado explicitamente

Defina a forma de um resultado de ator antes de escrever a lógica de detecção. Um modelo pequeno e tipado mantém todo mundo honesto sobre o que uma classificação realmente significa. As quatro classes usadas aqui são explicadas em 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;
}

Dois campos importam mais. actor é a classe. basis diz por que você acredita nisso. Um resultado de agente baseado em uma assinatura validada e um resultado de agente baseado em uma string de user agent são ambos "agente", mas merecem tratamentos muito diferentes depois, então você nunca deve fundi-los.

Etapa 3: Extraia evidências no middleware

A etapa de extração lê headers e o estado da requisição. Ainda não decide nada.

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",
  };
}

Algumas observações sobre este código:

  • signaturePresent registra apenas que headers de assinatura existem. A validação é uma etapa separada, coberta no guia sobre como verificar requisições assinadas de agentes.
  • A lista de tokens de user agent é intencionalmente simples. Ela produz uma declaração, nunca uma verificação.
  • x-app-assist representa qualquer mecanismo que seu frontend realmente envia quando um usuário trabalha por meio do recurso de IA embutido. Substitua isso pelo seu mecanismo real.

Etapa 4: Classifique com uma base declarada

Agora transforme evidências em um resultado. Mantenha as regras curtas, ordenadas da evidência mais forte para a mais fraca, e sempre caia para 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 };
}

Observe o que isso não faz. Nunca eleva uma declaração a uma verificação. Nunca atribui Human a uma requisição que não tem sinal interativo. Quando não consegue determinar, ele diz Unknown. Unknown é uma resposta válida e útil, e mostra exatamente onde termina sua visibilidade. Defendemos isso em detalhes em por que Unknown é um resultado válido para tráfego de IA.

Etapa 5: Integre em modo de observação

Monte o middleware cedo, anexe o resultado à requisição e registre-o. Não tome decisões com base nele.

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();
});

Três detalhes tornam isso seguro:

  1. O bloco try garante que uma falha na detecção nunca afete a resposta. A observação deve falhar aberto.
  2. O logging acontece em finish, então você captura o status code real e pode comparar depois as classificações com os resultados.
  3. verifySignature fica como stub para false até você implementar validação real. Isso mantém as requisições assinadas nas categorias declared ou unknown, em vez de exagerar sua classificação.

Etapa 6: Registre para análise, não apenas para depuração

Escreva logs que você possa agregar. Uma linha estruturada por requisição já basta.

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

Mantenha dados pessoais fora desses logs. Você precisa da rota, da classe, da base, dos motivos e do resultado. Não precisa de endereços IP completos, cookies brutos ou corpos de requisição para entender a composição do seu tráfego. Se mantiver qualquer identificador para correlação, faça hash com um salt rotativo e defina um período curto de retenção.

Etapa 7: Leia os dados antes de agir

Execute em modo de observação por tempo suficiente para ver os padrões semanais normais. Depois responda a algumas perguntas:

  • Quais rotas atraem agentes? Endpoints de documentação, catálogo e busca normalmente atraem. Fluxos de conta e checkout normalmente não deveriam.
  • Qual parcela dos resultados de agente é verificada versus declarada? Essa proporção mostra o quanto você pode confiar com segurança na identidade ao escrever políticas.
  • Onde Unknown está concentrado? Uma taxa alta de Unknown em uma rota geralmente significa que falta um sinal ali, por exemplo uma página que nunca executa o código do frontend.
  • Os resultados diferem por classe? Compare taxas de erro, etapas de conversão ou relatos de abuso entre classes. É daí que surgem as ideias reais de política.

As respostas muitas vezes surpreendem. As equipes descobrem com frequência que uma grande parte do que chamavam de "problema de bot" na verdade é automação legítima que usaria com prazer um endpoint estruturado, se ele existisse.

Etapa 8: Exponha o resultado para o restante da sua aplicação

Quando os dados estiverem fluindo, permita que os handlers leiam res.locals.actor para usos que não aplicam enforcement: tags de analytics, escolha de formato de resposta ou registro de que um ticket de suporte veio de uma sessão assistida por IA. Esses usos melhoram a experiência sem correr o risco de bloquear alguém indevidamente.

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 });
});

Enforcement, como challenges, rate limits ou step-up checks, vem depois, quando você já mediu falsos positivos. O guia sobre como implantar políticas cientes de ator cobre esse processo.

Quais erros você deve evitar?

  • Tratar o user agent como identidade. É o header mais fácil de alterar. Registre-o como uma declaração. Até operadores de IA dizem isso: a guidance for its hosted browser agent da Anthropic observa que qualquer pessoa pode enviar o mesmo header User-Agent e recomenda verificar seus endereços IP publicados antes de tratar o tráfego como genuíno.
  • Assumir Human por padrão. Se o seu código diz Human sempre que não encontra sinais de bot, seus números de Human vão ficar inflados e suas políticas vão confiar no tráfego errado.
  • Misturar classe e base. Sempre armazene os dois. Um dashboard que mostra apenas "Agent" esconde a diferença entre um operador verificado e uma string copiada.
  • Bloquear durante a observação. Até um bloqueio temporário invalida suas medições, porque clientes bloqueados mudam o comportamento.

Onde Proof of Actor entra

O middleware acima é um bom ponto de partida e mostra o modelo central: primeiro a evidência, uma base explícita para cada resultado, Unknown mantido visível e as decisões deixadas para sua aplicação. Proof of Actor se baseia no mesmo modelo, com cobertura mais profunda de sinais em contextos de servidor, borda, navegador e MCP, e com análises que permitem Detect, Understand e Route com confiança.

Se você quiser ajuda para levar isso adiante em uma aplicação em produção, candidate-se ao acesso antecipado. Para ver o fluxo completo de decisão, leia Como Proof of Actor funciona.

Fontes

Construa isso com a gente.

Participe do founding developer program do Proof of Actor.

Prefere falar primeiro? Fale com os fundadores