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:

PropriedadePropertyO que garanteWhat it guarantees
ConfidencialidadeConfidentialityninguém no meio do caminho lê o conteúdo (cookies, senhas, tokens)nobody along the way can read the content (cookies, passwords, tokens)
IntegridadeIntegrityninguém no meio do caminho altera o conteúdo sem ser percebidonobody along the way can alter the content undetected
AutenticidadeAuthenticityo 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)
navegadorbrowser servidorserver ClientHello ServerHello + certificadoServerHello + certificate chaves de sessão combinadassession keys agreed dados da aplicação, cifradosapplication data, encrypted Tudo depois da linha pontilhada está cifrado: método, caminho, cabeçalhos, cookies e corpo.Everything after the dashed line is encrypted: method, path, headers, cookies and body.
O handshake TLS (simplificado). Veja a nota de segurança e redes para capturar isso de verdade com o Wireshark.The TLS handshake (simplified). See the security and networking note to capture this for real with Wireshark.
HTTPS protege o transporte. Ele não impede SQL injection, não impede XSS, não valida nada que o usuário digita. É a base, não a proteção inteira — o resto desta nota cobre o que vem depois do TLS.HTTPS protects the transport. It does not prevent SQL injection, does not prevent XSS, does not validate anything the user types. It is the foundation, not the whole protection — the rest of this note covers what comes after TLS.

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çalhoHeaderFazDoes
Strict-Transport-Securitydiz 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-Policylista 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: nosniffimpede 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-ancestorsimpede 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-Policycontrola 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-Policydesliga 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.

Na prática, raramente se escreve cada cabeçalho à mão: frameworks como 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.

Secure só viaja em HTTPSonly travels over HTTPS HttpOnly JavaScript da página não consegue lerthe page's JavaScript cannot read it SameSite=Lax/Strict não viaja em requisições vindas de outro sitedoes not travel on requests coming from another site
Um cookie de sessão sem os três atributos é texto puro que qualquer script na página, ou qualquer site terceiro, pode tentar usar.A session cookie without the three attributes is plain text any script on the page, or any third-party site, can try to use.
Valor de SameSiteSameSite valueComportamentoBehaviour
Strictnunca 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
Laxenvia 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
Noneenvia 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)
Um erro real de 2025: um projeto de IA de código aberto configurou o cookie do refresh token com 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.

navegadorbrowser cookie: sid=a1b2c3 servidorserver Redis sid → user_id, exp consulta/confirmalook up/confirm O cookie não tem informação nenhuma por si só: fora de contexto, "a1b2c3" não diz nada. Revogar = apagar a linha no Redis.The cookie has no information on its own: out of context, "a1b2c3" means nothing. Revoking = delete the row in Redis.
A cada requisição, o servidor troca o ID do cookie por dados reais numa consulta.On every request, the server exchanges the cookie ID for real data in a lookup.
VantagemAdvantageCustoCost
Revogar é trivial: apague a linha e a sessão morre na horaRevoking is trivial: delete the row and the session dies instantlyprecisa 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 alltoda 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

eyJhbGciOiJIUzI1NiJ9 header algoritmo, tipo eyJzdWIiOiIxMjMiLCJleHAiOi4uLn0 payload claims: sub, exp, roles… SflKxwRJSMe... assinaturasignature prova que ninguém alterou o resto header.payload.signature — os dois primeiros são só Base64 (LEGÍVEIS por qualquer um, não cifrados); só a assinatura impede alteração.header.payload.signature — the first two are just Base64 (READABLE by anyone, not encrypted); only the signature prevents tampering.
Nunca coloque um segredo no payload de um JWT: qualquer pessoa pode decodificá-lo em jwt.io sem a chave.Never put a secret in a JWT's payload: anyone can decode it on jwt.io without the key.

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:

access token minutos · em cada requisiçãominutes · on every request refresh token dias/semanas · só para renovardays/weeks · only to renew expirou?expired? POST /refresh A resposta traz um access token novo (e, com rotação, um refresh token novo também) —The response carries a new access token (and, with rotation, a new refresh token too) — veja "rotação" logo abaixo.see "rotation" just below.
O access token vive pouco (o estrago de um vazamento é limitado pelo tempo); o refresh token vive mais, mas só serve para pedir um access token novo.The access token lives briefly (the damage from a leak is limited by time); the refresh token lives longer, but only serves to request a new access token.
Onde guardarWhere to storeRisco principalMain risk
localStorage/sessionStoragelocalStorage/sessionStorageacessí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/Laxinvisí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
Diferente do modelo stateful, revogar um JWT antes do tempo é difícil por definição — ele é válido até expirar, mesmo que o usuário troque a senha. Por isso: access tokens CURTOS (minutos) e, se precisar revogar de verdade e na hora, uma lista de bloqueio (denylist) consultada nas rotas sensíveis, ou volte para sessões stateful nelas.Unlike the stateful model, revoking a JWT early is hard by definition — it is valid until it expires, even if the user changes their password. Hence: SHORT access tokens (minutes) and, if you need to revoke immediately for real, a denylist checked on sensitive routes, or fall back to stateful sessions there.

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 revocationtrivial (apaga a linha)trivial (delete the row)difícil (precisa de denylist)hard (needs a denylist)
Escala entre serviçosScaling across servicesprecisa de um store compartilhadoneeds a shared storequalquer serviço valida sozinho, sem consultar nadaany service validates on its own, no lookup
Tamanho na requisiçãoRequest sizepequeno (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 caseaplicação monolítica, painel administrativoa monolithic app, an admin panelAPI pública, microsserviços, mobile, SSO entre domíniosa public API, microservices, mobile, SSO across domains
Um padrão comum e pragmático: sessão stateful para o site principal (login no navegador, cookie simples) e JWT só para a API pública ou para comunicação entre serviços internos. Não é incomum os dois convivendo no mesmo produto.A common, pragmatic pattern: stateful session for the main site (browser login, simple cookie) and JWT only for the public API or service-to-service communication. It is not unusual for both to coexist in the same product.

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.

você, logado em banco.comyou, logged into bank.com site-malicioso.comevil-site.com <form action="banco.com"><form action="bank.com"> banco.combank.com 1. visita1. visits 2. formulário AUTO-ENVIA2. form AUTO-SUBMITS (com os cookies de banco.com)(with bank.com's cookies)
O navegador não sabe que o pedido "não era bem-vindo" — ele só vê um formulário normal pedindo para enviar a banco.com, com os cookies certos em mãos.The browser does not know the request was "unwelcome" — it just sees a normal form asking to submit to bank.com, with the right cookies in hand.
DefesaDefenceComo funcionaHow it works
SameSite=Lax/Strict no cookieSameSite=Lax/Strict on the cookieo 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 headerrejeita 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/|safe só com conteúdo sanitizado.All HTML output uses the framework's default escaping; dangerouslySetInnerHTML/v-html/|safe only 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)

##RiscoRiskEm uma fraseIn one sentence
LLM01Prompt injectionPrompt injectiontexto 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
LLM02Vazamento de informação sensívelSensitive information disclosureo modelo repete dados confidenciais do contexto, do treino ou do históricothe model repeats confidential data from context, training or history
LLM03Cadeia de suprimentosSupply chainum modelo, plugin ou dataset de terceiros comprometido entra na sua aplicaçãoa compromised third-party model, plugin or dataset enters your application
LLM04Envenenamento de dados/modeloData and model poisoningdados de treino ou de contexto manipulados para mudar o comportamento do modelotraining or context data manipulated to change the model's behaviour
LLM05Tratamento inadequado da saídaImproper output handlinga 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)
LLM06Agência excessivaExcessive agencyo agente tem mais permissão ou autonomia do que a tarefa precisathe agent has more permission or autonomy than the task needs
LLM07Vazamento do prompt de sistemaSystem prompt leakageinstruções internas (às vezes com segredos) são extraídas pelo usuáriointernal instructions (sometimes with secrets) are extracted by the user
LLM08Fraquezas de vetores/embeddingsVector and embedding weaknessesataques contra o pipeline de RAG e o banco vetorialattacks against the RAG pipeline and the vector database
LLM09DesinformaçãoMisinformationo 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
LLM10Consumo ilimitadoUnbounded consumptionnada 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:

vocêyou "resuma este e-mail""summarize this email" agente de IAAI agent lê o e-mail como contextoreads the email as context e-mailemail "ignore tudo e..."ignore everything and... ← instrução maliciosa ESCONDIDA no dado que o agente só deveria LER← malicious instruction HIDDEN in data the agent was only meant to READ
Injeção indireta: o atacante nunca fala com o agente diretamente — ele planta a instrução em algo que o agente vai processar depois, por conta própria.Indirect injection: the attacker never talks to the agent directly — they plant the instruction in something the agent will process later, on its own.
TipoTypeComo funcionaHow it works
DiretaDirecto 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
IndiretaIndirecta 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:

RiscoRiskExemploExample
Envenenamento de ferramenta (tool poisoning)Tool poisoninga 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 deputyum 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 passthroughum 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 chainum 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
RAG e fine-tuning não resolvem prompt injection — eles aterram o modelo em dados melhores, mas não o tornam seguro contra instruções hostis misturadas nesses mesmos dados. É um erro comum tratar "o modelo é bom" como defesa de segurança.RAG and fine-tuning do not fix prompt injection — they ground the model in better data, but they do not make it secure against hostile instructions mixed into that same data. It is a common mistake to treat "the model is good" as a security control.

Defesas que funcionam hojeDefences that work today

PrincípioPrincipleNa práticaIn practice
Privilégio mínimo nas ferramentasLeast privilege on toolso 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 actionsmandar 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 instructionsum 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/sandboxingcó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 actingse 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/connecttrate 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 limitstetos 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 headerscurl -sI https://exemplo.com
testar a nota de um site em segurança de cabeçalhosgrade a site's header securitysecurityheaders.com
revogar uma sessão na horarevoke a session immediatelyuse 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 safelycookie HttpOnly + Secure + SameSite=Strictan HttpOnly + Secure + SameSite=Strict cookie
proteger contra CSRFprotect against CSRFSameSite=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 agentprivilégio mínimo + confirmação humana para ações sensíveisleast privilege + human confirmation for sensitive actions

Perguntas de revisãoReview questions

PerguntaQuestionRespostaAnswer
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.