Study guide
Web apps, protected end to end.
From HTTPS and security headers to stateful sessions, stateless JWTs, CSRF, injection — and the new frontier of AI agent security.
HTTP e HTTPS: a baseHTTP and HTTPS: the base
Esta nota é o lado de dentro da muralha: como proteger a aplicação web que você constrói, não como atacar a de outra pessoa (isso fica na nota de Red Team). O fio condutor é simples: HTTP é um protocolo de texto, sem memória entre uma requisição e a próxima; tudo que chamamos de "segurança de app web" é uma camada de proteções em cima dessa base simples — cifrar o transporte, autenticar quem está do outro lado, lembrar quem é quem entre requisições, e desconfiar de qualquer dado que entra.This note is the inside of the wall: how to protect the web app you build, not how to attack someone else's (that is the Red Team note). The thread running through it is simple: HTTP is a text protocol with no memory between one request and the next; everything we call "web app security" is a layer of protections on top of that simple base — encrypting the transport, authenticating who is on the other side, remembering who is who between requests, and distrusting any data that comes in.
HTTP e HTTPS: a mesma coisa, uma delas cifradaHTTP and HTTPS: the same thing, one of them encrypted
HTTP descreve o que é trocado (método, cabeçalhos, corpo); HTTPS é HTTP rodando dentro de uma conexão TLS, que cifra e autentica o transporte. A nota de segurança e redes já mostrou isso na prática: uma captura de pacotes da porta 80 mostra a requisição em texto puro; da porta 443, só ruído. TLS resolve três problemas de uma vez:HTTP describes what is exchanged (method, headers, body); HTTPS is HTTP running inside a TLS connection, which encrypts and authenticates the transport. The security and networking note already showed this in practice: a packet capture of port 80 shows the request in plain text; of port 443, just noise. TLS solves three problems at once:
| PropriedadeProperty | O que garanteWhat it guarantees |
|---|---|
| ConfidencialidadeConfidentiality | ninguém no meio do caminho lê o conteúdo (cookies, senhas, tokens)nobody along the way can read the content (cookies, passwords, tokens) |
| IntegridadeIntegrity | ninguém no meio do caminho altera o conteúdo sem ser percebidonobody along the way can alter the content undetected |
| AutenticidadeAuthenticity | o certificado prova que você está falando com o servidor certo, não um impostor (o "s" de HTTPS)the certificate proves you are talking to the right server, not an impostor (the "s" in HTTPS) |
Cabeçalhos de segurançaSecurity headers
O navegador obedece a instruções que o SERVIDOR manda em cabeçalhos HTTP de resposta. Um punhado deles fecha a maior parte das portas de ataque mais comuns, de graça, sem mudar uma linha de lógica de negócio.The browser obeys instructions the SERVER sends in HTTP response headers. A handful of them close most of the common attack doors, for free, without changing a line of business logic.
| CabeçalhoHeader | FazDoes |
|---|---|
Strict-Transport-Security | diz ao navegador para NUNCA mais tentar HTTP nesse domínio, nem seguindo um link — fecha a janela de um downgrade para texto purotells the browser to NEVER try HTTP on this domain again, even via a link — closes the window for a downgrade to plain text |
Content-Security-Policy | lista de onde scripts, estilos e outros recursos podem vir; a defesa mais forte contra XSS que existe no navegadora whitelist of where scripts, styles and other resources may come from; the strongest browser-side defence against XSS |
X-Content-Type-Options: nosniff | impede o navegador de "adivinhar" o tipo de um arquivo e executá-lo como script por enganostops the browser from "guessing" a file's type and running it as script by mistake |
X-Frame-Options / frame-ancestors | impede que o seu site seja carregado dentro de um <iframe> de outro site (clickjacking)stops your site from being loaded inside another site's <iframe> (clickjacking) |
Referrer-Policy | controla quanto da URL de origem vaza para o próximo site quando alguém clica num link seucontrols how much of the originating URL leaks to the next site when someone clicks a link of yours |
Permissions-Policy | desliga recursos do navegador (câmera, geolocalização, microfone) que a página não usaturns off browser features (camera, geolocation, microphone) the page does not use |
CORS: a exceção controlada à regra de mesma origemCORS: the controlled exception to same-origin
Por padrão, o navegador bloqueia JavaScript de um site lendo a resposta de outro (a same-origin policy). Access-Control-Allow-Origin e companhia são o jeito do SERVIDOR abrir uma exceção explícita para origens específicas. Access-Control-Allow-Origin: * é seguro para uma API pública e sem cookies; para uma API autenticada, liste as origens exatas e use Access-Control-Allow-Credentials: true só quando precisar enviar cookies entre origens.By default, the browser blocks JavaScript from one site reading another site's response (the same-origin policy). Access-Control-Allow-Origin and friends are how the SERVER opens an explicit exception for specific origins. Access-Control-Allow-Origin: * is fine for a public, cookie-free API; for an authenticated API, list exact origins and use Access-Control-Allow-Credentials: true only when you need to send cookies cross-origin.
Exercício: ver e testar os cabeçalhos de um appExercise: see and test an app's headers
mkdir lab-headers && cd lab-headers
cat > server.js <<'EOF'
const http = require('http');
http.createServer((req, res) => {
res.writeHead(200, {
'Content-Type': 'text/html',
'Strict-Transport-Security': 'max-age=63072000; includeSubDomains',
'Content-Security-Policy': "default-src 'self'; script-src 'self'",
'X-Content-Type-Options': 'nosniff',
'X-Frame-Options': 'DENY',
'Referrer-Policy': 'strict-origin-when-cross-origin',
'Permissions-Policy': 'camera=(), microphone=(), geolocation=()',
});
res.end('<h1>ola</h1>');
}).listen(3000);
EOF
cat > Dockerfile <<'EOF'
FROM node:22-slim
COPY server.js .
CMD ["node", "server.js"]
EOF
docker build -t lab-headers . && docker run -d -p 127.0.0.1:3000:3000 lab-headers
curl -sI http://localhost:3000 | grep -iE "strict-transport|content-security|x-content-type|x-frame|referrer|permissions"
Resultado esperado: o curl -I (só cabeçalhos) mostra todas as seis linhas configuradas no servidor. Compare depois com um site real: abra o DevTools do navegador → aba Network → clique na primeira requisição → "Headers", ou rode o mesmo curl -sI contra https://baselarte.com.Expected result: curl -I (headers only) shows all six lines configured on the server. Afterwards compare with a real site: open the browser DevTools → Network tab → click the first request → "Headers", or run the same curl -sI against https://baselarte.com.
helmet (Node/Express), django-csp (Django) ou o próprio proxy reverso (Nginx, Caddy, Cloudflare) aplicam um conjunto sensato por padrão. O scanner gratuito securityheaders.com testa qualquer site público e dá uma nota.In practice, headers are rarely hand-written one by one: frameworks like helmet (Node/Express), django-csp (Django) or the reverse proxy itself (Nginx, Caddy, Cloudflare) apply a sane set by default. The free scanner securityheaders.com tests any public site and gives it a grade.Cookies: os três atributos que importamCookies: the three attributes that matter
Um cookie é um pedacinho de texto que o servidor pede ao navegador para guardar e devolver em toda requisição futura ao mesmo site. É o mecanismo mais comum para "lembrar" quem está logado — e, mal configurado, uma das formas mais fáceis de vazar uma sessão inteira.A cookie is a small piece of text the server asks the browser to store and send back on every future request to the same site. It is the most common mechanism to "remember" who is logged in — and, misconfigured, one of the easiest ways to leak a whole session.
| Valor de SameSiteSameSite value | ComportamentoBehaviour |
|---|---|
Strict | nunca envia o cookie numa navegação vinda de outro site, nem clicando num link — mais seguro, pode quebrar fluxos de login vindos de e-mailnever sends the cookie on navigation coming from another site, not even via a clicked link — safest, can break login flows arriving from email |
Lax | envia em navegação de nível superior (clicar num link), mas não em requisições de fundo (imagem, formulário POST de outro site) — o equilíbrio padrão para cookies de autenticaçãosends on top-level navigation (clicking a link), but not on background requests (image, a POST form from another site) — the default balance for authentication cookies |
None | envia sempre, inclusive entre sites — exige Secure junto; use só quando o cookie PRECISA cruzar origens (ex.: um widget embutido)always sends, including cross-site — requires Secure alongside it; use only when the cookie MUST cross origins (e.g. an embedded widget) |
SameSite: 'None' combinado com CORS permissivo demais — a combinação deixou o token alcançável de qualquer origem. SameSite=None num cookie de autenticação quase sempre é um sinal de alerta.A real 2025 mistake: an open-source AI project configured its refresh-token cookie with SameSite: 'None' combined with overly permissive CORS — the combination left the token reachable from any origin. SameSite=None on an authentication cookie is almost always a red flag.Fixação de sessãoSession fixation
Um ataque simples: o atacante planta um ID de sessão conhecido na vítima (por um link, ou porque o site aceita qualquer ID enviado) e espera ela fazer login — se o site mantém o MESMO ID depois do login, o atacante agora está "logado" também. A defesa: sempre gerar um ID de sessão novo no momento do login (e de novo em qualquer mudança de privilégio), nunca aceitar um ID de sessão que já veio do cliente antes de autenticar.A simple attack: the attacker plants a known session ID on the victim (via a link, or because the site accepts any submitted ID) and waits for them to log in — if the site keeps the SAME ID after login, the attacker is now "logged in" too. The defence: always generate a new session ID at the moment of login (and again on any privilege change), never accept a session ID that arrived from the client before authenticating.
Autenticação stateful: sessões no servidorStateful authentication: server-side sessions
Autenticação stateful (com estado): o servidor guarda, num banco ou cache (Redis é o padrão de mercado), um registro de cada sessão ativa. O cookie carrega só um ID opaco e aleatório — o resto da informação (quem é o usuário, quando expira) fica no servidor.Stateful authentication: the server keeps, in a database or cache (Redis is the market standard), a record of each active session. The cookie carries only an opaque, random ID — the rest of the information (who the user is, when it expires) lives on the server.
| VantagemAdvantage | CustoCost |
|---|---|
| Revogar é trivial: apague a linha e a sessão morre na horaRevoking is trivial: delete the row and the session dies instantly | precisa de um banco/cache compartilhado — escalar horizontalmente exige um Redis central, não memória localneeds a shared database/cache — scaling horizontally requires a central Redis, not local memory |
| O cookie não carrega dado nenhum sensívelThe cookie carries no sensitive data at all | toda requisição faz uma consulta extra (mitigável com cache)every request makes an extra lookup (mitigable with caching) |
Logout, na práticaLogout, in practice
# pseudocódigo do lado do servidor
POST /login -> gera sid aleatório, grava {user_id, criado_em} no Redis, seta cookie Secure+HttpOnly+SameSite=Lax
GET /perfil -> lê sid do cookie, busca no Redis, 401 se não achar ou expirado
POST /logout -> apaga a chave sid no Redis, expira o cookie no navegador
Esse é o ponto central: logout stateful é só DELETE. Isso fica bem mais difícil no modelo stateless, que vem a seguir.This is the key point: stateful logout is just a DELETE. This gets much harder in the stateless model, which comes next.
Autenticação stateless: JWT, access e refreshStateless authentication: JWT, access and refresh
Autenticação stateless (sem estado): em vez de um ID opaco, o servidor assina um token que carrega os dados nele mesmo — tipicamente um JWT (JSON Web Token). Qualquer servidor que conheça a chave de assinatura pode validar o token sozinho, sem consultar banco nenhum. É o modelo padrão para APIs e microsserviços, e para SPAs que falam com uma API separada.Stateless authentication: instead of an opaque ID, the server signs a token that carries the data inside itself — typically a JWT (JSON Web Token). Any server that knows the signing key can validate the token on its own, with no database lookup. It is the standard model for APIs and microservices, and for SPAs talking to a separate API.
A anatomia de um JWTThe anatomy of a JWT
Access token curto + refresh token longoShort access token + long refresh token
O padrão que resolve o maior problema do modelo stateless (não dá para "apagar" um token já emitido) é usar DOIS tokens com vidas diferentes:The pattern that solves stateless's biggest problem (you cannot "delete" an already-issued token) is using TWO tokens with different lifetimes:
| Onde guardarWhere to store | Risco principalMain risk |
|---|---|
localStorage/sessionStoragelocalStorage/sessionStorage | acessível por QUALQUER script na página — um XSS rouba o token inteiroreadable by ANY script on the page — one XSS steals the whole token |
Cookie HttpOnly + Secure + SameSite=Strict/LaxCookie HttpOnly + Secure + SameSite=Strict/Lax | invisível a scripts (mitiga XSS), mas exige proteção de CSRF para o endpoint que o lêinvisible to scripts (mitigates XSS), but needs CSRF protection for the endpoint that reads it |
| Memória (variável JS, nunca persistida)Memory (a JS variable, never persisted) | mais seguro para o access token; some ao recarregar a página (por isso existe o refresh token)safest for the access token; disappears on page reload (which is exactly why the refresh token exists) |
Padrão recomendado hoje: access token em memória, refresh token num cookie HttpOnly+Secure+SameSite=Strict. Evite localStorage para qualquer um dos dois se puder.Today's recommended pattern: access token in memory, refresh token in an HttpOnly+Secure+SameSite=Strict cookie. Avoid localStorage for either one if you can.
Rotação de refresh token e detecção de reusoRefresh token rotation and reuse detection
A cada uso, o refresh token é TROCADO por um novo, e o antigo é invalidado. Se o antigo aparecer de novo (alguém tentando reusar um token roubado, ou uma cópia antiga), isso significa que ele vazou — a resposta correta é revogar a CADEIA inteira daquele token, derrubando a sessão.On every use, the refresh token is SWAPPED for a new one, and the old one is invalidated. If the old one shows up again (someone trying to reuse a stolen token, or an old copy), that means it leaked — the correct response is to revoke the WHOLE chain for that token, killing the session.
# pseudocódigo do endpoint de refresh
POST /refresh (cookie: refresh_token=rt_antigo)
1. valida assinatura e expiração de rt_antigo
2. rt_antigo já foi usado antes (está na lista de "já trocados")?
SIM -> REUSO DETECTADO: revoga toda a família de tokens desse usuário, força novo login
NÃO -> gera access_token novo (minutos) + refresh_token novo (dias)
marca rt_antigo como "já trocado", responde com os dois
Qual escolherWhich one to choose
Nenhum dos dois é "o certo"; são otimizados para coisas diferentes.Neither one is "the right one"; they are optimised for different things.
| Stateful (sessão)Stateful (session) | Stateless (JWT)Stateless (JWT) | |
|---|---|---|
| Revogação imediataImmediate revocation | trivial (apaga a linha)trivial (delete the row) | difícil (precisa de denylist)hard (needs a denylist) |
| Escala entre serviçosScaling across services | precisa de um store compartilhadoneeds a shared store | qualquer serviço valida sozinho, sem consultar nadaany service validates on its own, no lookup |
| Tamanho na requisiçãoRequest size | pequeno (só o ID)small (just the ID) | maior (o token inteiro, em cada chamada)bigger (the whole token, on every call) |
| Caso de uso típicoTypical use case | aplicação monolítica, painel administrativoa monolithic app, an admin panel | API pública, microsserviços, mobile, SSO entre domíniosa public API, microservices, mobile, SSO across domains |
CSRF: o pedido que você não pediuCSRF: the request you did not ask for
Cross-Site Request Forgery: um site malicioso faz o SEU navegador mandar uma requisição para um site onde você já está logado, usando seus cookies automaticamente — sem roubar nada, só abusando do fato de que o navegador sempre anexa os cookies do domínio.Cross-Site Request Forgery: a malicious site makes YOUR browser send a request to a site where you are already logged in, using your cookies automatically — without stealing anything, just abusing the fact that the browser always attaches the domain's cookies.
| DefesaDefence | Como funcionaHow it works |
|---|---|
SameSite=Lax/Strict no cookieSameSite=Lax/Strict on the cookie | o navegador simplesmente NÃO anexa o cookie numa requisição de fundo vinda de outro site — resolve a maior parte dos casos sozinho, hojethe browser simply does NOT attach the cookie on a background request coming from another site — solves most cases on its own, today |
| Token CSRF (synchronizer token)CSRF token (synchronizer token) | um valor aleatório, único por sessão/formulário, que o site malicioso não tem como adivinhar ou ler (same-origin policy o impede)a random, per-session/per-form value the malicious site has no way to guess or read (same-origin policy blocks it) |
Checar o cabeçalho Origin/RefererChecking the Origin/Referer header | rejeita requisições sensíveis cuja origem não bate com a esperada — uma camada extra, não a únicarejects sensitive requests whose origin does not match what is expected — an extra layer, not the only one |
SameSite=Lax (o padrão em navegadores modernos quando nada é especificado) já neutraliza boa parte do CSRF clássico. Ainda assim, APIs com estado que mudam dados (POST/PUT/DELETE) continuam se beneficiando de um token CSRF como segunda camada — "defesa em profundidade", o mesmo princípio da nota de Linux.SameSite=Lax (the default in modern browsers when nothing is specified) already neutralises much of classic CSRF. Still, state-changing APIs (POST/PUT/DELETE) keep benefiting from a CSRF token as a second layer — "defence in depth", the same principle from the Linux note.SQL injection e XSS: o lado de quem constróiSQL injection and XSS: the builder's side
Confiar em qualquer dado que entra de fora é a raiz da maioria das falhas de aplicação web. As duas mais comuns já apareceram na nota de Red Team do ponto de vista de quem ataca; aqui é o lado de quem constrói.Trusting any data that comes in from outside is the root of most web application flaws. The two most common ones already showed up in the Red Team note from the attacker's side; here is the builder's side.
SQL injection: nunca monte SQL colando textoSQL injection: never build SQL by pasting text
-- NUNCA: concatena a entrada direto na consulta
const query = `SELECT * FROM usuarios WHERE email = '${emailDoFormulario}'`;
-- SEMPRE: consulta parametrizada — a entrada é um VALOR, nunca parte do comando
SELECT * FROM usuarios WHERE email = $1; -- Postgres (driver node-postgres, por exemplo)
Veja a nota de Postgres para os comandos e a seção de exploração da nota de Red Team para como essa falha é usada do outro lado. ORMs (Prisma, SQLAlchemy, ActiveRecord) fazem isso por padrão — o risco real aparece quando alguém usa SQL "cru" dentro do ORM para um caso especial e esquece de parametrizar.See the Postgres note for commands and the Red Team note's exploitation section for how this flaw is used from the other side. ORMs (Prisma, SQLAlchemy, ActiveRecord) do this by default — the real risk shows up when someone drops down to "raw" SQL inside the ORM for a special case and forgets to parameterise.
XSS: escapar saída, não só validar entradaXSS: escape output, not just validate input
Frameworks modernos (React, Vue, Angular) escapam a saída por padrão quando você usa a sintaxe normal de template — o perigo mora nas "válvulas de escape" que pulam essa proteção de propósito.Modern frameworks (React, Vue, Angular) escape output by default when you use their normal template syntax — the danger lives in the "escape hatches" that skip that protection on purpose.
// React: ESCAPADO automaticamente, seguro mesmo se bio vier de um usuário
<p>{usuario.bio}</p>
// React: a válvula de escape — NUNCA com texto de usuário sem sanitizar antes
<div dangerouslySetInnerHTML={{ __html: usuario.bio }} />
Se você realmente precisa renderizar HTML vindo do usuário (um editor rich-text, por exemplo), sanitize com uma biblioteca testada para isso (como DOMPurify) — nunca confie em regex caseira para "limpar" HTML.If you genuinely need to render user-supplied HTML (a rich-text editor, for example), sanitise it with a battle-tested library (like DOMPurify) — never trust a homemade regex to "clean" HTML.
Uma checklist curtaA short checklist
- Toda consulta com dado externo é parametrizada; nenhum SQL é montado por concatenação de string.Every query with external data is parameterised; no SQL is built by string concatenation.
- Toda saída em HTML usa a escapagem padrão do framework;
dangerouslySetInnerHTML/v-html/|safesó com conteúdo sanitizado.All HTML output uses the framework's default escaping;dangerouslySetInnerHTML/v-html/|safeonly with sanitised content. - Validação de entrada acontece no SERVIDOR, mesmo que também exista no navegador (o navegador é só conforto para o usuário, não é segurança).Input validation happens on the SERVER, even if it also exists in the browser (the browser is just user convenience, not security).
- Um WAF e um Content-Security-Policy são camadas extras, nunca a única defesa.A WAF and a Content-Security-Policy are extra layers, never the only defence.
A nova fronteira: segurança de agentes de IAThe new frontier: AI agent security
Tudo até aqui assume que quem fala com a sua aplicação é um navegador controlado por uma pessoa. A nova peça é um agente de IA: um modelo de linguagem que lê conteúdo, decide, e age — chama APIs, roda código, lê e-mails, usa ferramentas via protocolos como o MCP (Model Context Protocol). Isso muda o modelo de ameaça: o "usuário" que manda instruções pode agora ser um texto que o próprio agente leu em algum lugar.Everything so far assumes whoever talks to your application is a browser controlled by a person. The new piece is an AI agent: a language model that reads content, decides, and acts — calls APIs, runs code, reads emails, uses tools through protocols like MCP (Model Context Protocol). This changes the threat model: the "user" issuing instructions can now be a piece of text the agent itself read somewhere.
O OWASP Top 10 para aplicações LLM (2025)The OWASP Top 10 for LLM Applications (2025)
| ## | RiscoRisk | Em uma fraseIn one sentence |
|---|---|---|
| LLM01 | Prompt injectionPrompt injection | texto malicioso faz o modelo ignorar suas instruções originais e seguir as de outra pessoamalicious text makes the model ignore its original instructions and follow someone else's |
| LLM02 | Vazamento de informação sensívelSensitive information disclosure | o modelo repete dados confidenciais do contexto, do treino ou do históricothe model repeats confidential data from context, training or history |
| LLM03 | Cadeia de suprimentosSupply chain | um modelo, plugin ou dataset de terceiros comprometido entra na sua aplicaçãoa compromised third-party model, plugin or dataset enters your application |
| LLM04 | Envenenamento de dados/modeloData and model poisoning | dados de treino ou de contexto manipulados para mudar o comportamento do modelotraining or context data manipulated to change the model's behaviour |
| LLM05 | Tratamento inadequado da saídaImproper output handling | a saída do modelo é usada sem validação (vira SQL, HTML, comando de shell)the model's output is used without validation (becomes SQL, HTML, a shell command) |
| LLM06 | Agência excessivaExcessive agency | o agente tem mais permissão ou autonomia do que a tarefa precisathe agent has more permission or autonomy than the task needs |
| LLM07 | Vazamento do prompt de sistemaSystem prompt leakage | instruções internas (às vezes com segredos) são extraídas pelo usuáriointernal instructions (sometimes with secrets) are extracted by the user |
| LLM08 | Fraquezas de vetores/embeddingsVector and embedding weaknesses | ataques contra o pipeline de RAG e o banco vetorialattacks against the RAG pipeline and the vector database |
| LLM09 | DesinformaçãoMisinformation | o modelo apresenta algo errado com confiança (alucinação) e isso vira uma decisãothe model confidently states something wrong (hallucination) and it becomes a decision |
| LLM10 | Consumo ilimitadoUnbounded consumption | nada limita quantas chamadas/tokens/custo um usuário ou agente pode gerarnothing limits how many calls/tokens/cost a user or agent can generate |
Prompt injection, de pertoPrompt injection, up close
É o risco #1 da lista, e o mais diferente de tudo que veio antes nesta nota: não existe "escapar a entrada" que resolva, porque a instrução maliciosa e o conteúdo legítimo chegam pelo MESMO canal — texto em linguagem natural. Dois sabores:It is risk #1 on the list, and the one most unlike everything else in this note: there is no "escape the input" fix, because the malicious instruction and the legitimate content arrive through the SAME channel — natural-language text. Two flavours:
| TipoType | Como funcionaHow it works |
|---|---|
| DiretaDirect | o próprio usuário escreve algo como "ignore as instruções anteriores e..." direto no chatthe user themself types something like "ignore previous instructions and..." straight into the chat |
| IndiretaIndirect | a instrução está escondida em algo que o agente vai LER depois: um e-mail, uma página web, um arquivo, a descrição de uma ferramenta MCP — o usuário nem sabe que ela está láthe instruction is hidden in something the agent will READ later: an email, a web page, a file, an MCP tool description — the user does not even know it is there |
Agência excessiva e os riscos específicos do MCPExcessive agency and MCP-specific risks
Quando o agente só conversa, o pior caso é uma resposta errada. Quando ele tem ferramentas — manda e-mail, acessa um banco, roda código, chama uma API de pagamento — o pior caso é uma AÇÃO errada. O MCP (o protocolo mais comum hoje para conectar agentes a ferramentas) trouxe à tona uma família nova de riscos em 2025:When the agent only talks, the worst case is a wrong answer. When it has tools — sends email, accesses a database, runs code, calls a payment API — the worst case is a wrong ACTION. MCP (today's most common protocol for connecting agents to tools) surfaced a new family of risks in 2025:
| RiscoRisk | ExemploExample |
|---|---|
| Envenenamento de ferramenta (tool poisoning)Tool poisoning | a DESCRIÇÃO de uma ferramenta MCP (que o modelo lê, mas o usuário normalmente não vê) esconde instruções — o servidor pode atacar antes mesmo de qualquer ferramenta ser chamadaan MCP tool's DESCRIPTION (which the model reads but the user usually never sees) hides instructions — the server can attack before any tool is even called |
| Confused deputyConfused deputy | um servidor MCP usa os PRÓPRIOS privilégios elevados em vez de agir em nome de quem pediu, fazendo algo que o usuário não tinha permissão de fazeran MCP server uses its OWN elevated privileges instead of acting on behalf of the requester, doing something the user was not allowed to do |
| Repasse de token (token passthrough)Token passthrough | um servidor encaminha um token emitido para UM serviço a uma API diferente — a especificação do MCP proíbe isso explicitamentea server forwards a token issued for ONE service to a different API — the MCP spec explicitly forbids this |
| "Rug pull" / cadeia de suprimentos"Rug pull" / supply chain | um pacote MCP passa por revisão com comportamento legítimo e, numa atualização POSTERIOR, muda — em setembro de 2025 um servidor MCP de e-mail amplamente instalado passou a copiar e-mails enviados pelo agente para um endereço do atacantean MCP package passes review behaving legitimately and, in a LATER update, changes — in September 2025 a widely installed email MCP server started copying emails sent through the agent to an attacker-controlled address |
Defesas que funcionam hojeDefences that work today
| PrincípioPrinciple | Na práticaIn practice |
|---|---|
| Privilégio mínimo nas ferramentasLeast privilege on tools | o agente só tem acesso às ferramentas e escopos que a tarefa específica precisa — nunca uma chave de API "para tudo"the agent only has access to the tools and scopes the specific task needs — never an "everything" API key |
| Humano no laço para ações sensíveisHuman in the loop for sensitive actions | mandar dinheiro, apagar dados, enviar e-mail em massa: peça confirmação explícita antes de executar, não só antes de "sugerir"sending money, deleting data, mass-emailing: ask for explicit confirmation before executing, not just before "suggesting" |
| Tratar todo conteúdo lido como dado, nunca como instruçãoTreat all content read as data, never as instructions | um e-mail, uma página web ou o resultado de uma ferramenta NUNCA deveriam poder mudar o comportamento do agente da mesma forma que o pedido original do usuárioan email, a web page or a tool result should NEVER be able to change the agent's behaviour the way the original user request can |
| Isolamento/sandboxingIsolation/sandboxing | código gerado por um agente roda num contêiner descartável, sem acesso à rede ou a segredos reais por padrãoagent-generated code runs in a throwaway container, with no network or real-secret access by default |
| Validar a saída antes de agirValidate output before acting | se a saída do modelo vira uma chamada de API, trate-a como entrada não confiável: valide o formato e os limites antes de executarif the model's output becomes an API call, treat it as untrusted input: validate the shape and the limits before executing |
| Revisão do que o agente pode instalar/conectarReview of what the agent can install/connect | trate um novo servidor MCP como uma nova dependência de produção: fixe a versão, audite antes de atualizar (o caso do "rug pull" acima só funcionou porque ninguém revisou a atualização)treat a new MCP server like a new production dependency: pin the version, audit before upgrading (the "rug pull" case above only worked because nobody reviewed the update) |
| Limites de usoUsage limits | tetos de custo, de chamadas e de tempo por agente/usuário, para o LLM10 (consumo ilimitado) nunca virar uma conta surpresa ou uma negação de serviçocost, call and time caps per agent/user, so LLM10 (unbounded consumption) never turns into a surprise bill or a denial of service |
O paralelo com o resto desta nota: um agente com uma ferramenta de banco de dados sem privilégio mínimo é o mesmo erro que uma aplicação conectando como super-usuário do Postgres (nota de Postgres); confiar cegamente no texto que um agente leu é o mesmo erro de confiar na entrada de um usuário sem validar (seção de SQL injection e XSS acima). O princípio não muda; a superfície de ataque é que é nova.The parallel with the rest of this note: an agent with a database tool and no least privilege is the same mistake as an application connecting as a Postgres superuser (the Postgres note); blindly trusting text an agent read is the same mistake as trusting user input without validation (the SQL injection and XSS section above). The principle does not change; it is the attack surface that is new.
Cola e revisãoCheat sheet and review
ColaCheat sheet
| Quero…I want… | FaçaDo |
|---|---|
| ver os cabeçalhos de um sitesee a site's headers | curl -sI https://exemplo.com |
| testar a nota de um site em segurança de cabeçalhosgrade a site's header security | securityheaders.com |
| revogar uma sessão na horarevoke a session immediately | use sessão stateful (apagar do Redis), ou JWT + denylistuse a stateful session (delete from Redis), or JWT + denylist |
| guardar um refresh token com segurançastore a refresh token safely | cookie HttpOnly + Secure + SameSite=Strictan HttpOnly + Secure + SameSite=Strict cookie |
| proteger contra CSRFprotect against CSRF | SameSite=Lax/Strict + token CSRF em rotas que mudam dadosSameSite=Lax/Strict + a CSRF token on state-changing routes |
| dar uma ferramenta a um agente de IA com segurançasafely give a tool to an AI agent | privilégio mínimo + confirmação humana para ações sensíveisleast privilege + human confirmation for sensitive actions |
Perguntas de revisãoReview questions
| PerguntaQuestion | RespostaAnswer |
|---|---|
| O que o HTTPS protege, e o que ele NÃO protege?What does HTTPS protect, and what does it NOT protect? | Protege o transporte (confidencialidade, integridade, autenticidade); não protege contra SQL injection, XSS ou entrada não validada.It protects the transport (confidentiality, integrity, authenticity); it does not protect against SQL injection, XSS or unvalidated input. |
| Qual cabeçalho é a defesa mais forte do navegador contra XSS?Which header is the browser's strongest defence against XSS? | Content-Security-Policy.Content-Security-Policy. |
Por que SameSite=None num cookie de autenticação é um sinal de alerta?Why is SameSite=None on an authentication cookie a red flag? | Porque o cookie passa a viajar em requisições de QUALQUER origem, abrindo a porta para CSRF.Because the cookie starts travelling on requests from ANY origin, opening the door to CSRF. |
| Qual a diferença central entre revogar uma sessão stateful e um JWT?What is the key difference between revoking a stateful session and a JWT? | Sessão: apagar uma linha no servidor. JWT: válido até expirar, a menos que haja uma denylist.Session: delete a row on the server. JWT: valid until it expires, unless there is a denylist. |
| Por que o access token deveria ter vida curta?Why should the access token be short-lived? | Porque não dá para revogá-lo antes da hora; uma vida curta limita o estrago de um vazamento.Because it cannot be revoked early; a short life limits the damage of a leak. |
| O que diferencia prompt injection direta de indireta?What tells direct and indirect prompt injection apart? | Direta: o usuário escreve a instrução maliciosa. Indireta: ela está escondida em algo que o agente lê (e-mail, página, ferramenta).Direct: the user types the malicious instruction. Indirect: it is hidden in something the agent reads (email, page, tool). |
| Por que um bom RAG não resolve prompt injection?Why doesn't a good RAG fix prompt injection? | Porque aterra o modelo em dados melhores, mas não o protege de instruções hostis escondidas nesses mesmos dados.Because it grounds the model in better data, but does not protect it from hostile instructions hidden in that same data. |
- OWASP Top 10 (web application security risks).
- OWASP Cheat Sheet Series. Session Management (session fixation, cookie attributes).
- OWASP Cheat Sheet Series. JSON Web Token security.
- OWASP Cheat Sheet Series. Cross-Site Request Forgery Prevention.
- OWASP Secure Headers Project.
- IETF. OAuth 2.1 (draft) — refresh token rotation guidance.
- Auth0 documentation. Refresh token rotation and reuse detection.
- OWASP GenAI Security Project. Top 10 for LLM Applications 2025.
- Model Context Protocol specification (token passthrough prohibition, security considerations).
- Simon Willison. Model Context Protocol has prompt injection security problems (on Invariant Labs' tool-poisoning research).
- MDN Web Docs. HTTP headers reference.