Desenvolvimento seguro na prática: SDLC seguro e threat modeling com STRIDE
Como transformar segurança em parte do ciclo de desenvolvimento com shift left, threat modeling e STRIDE, em vez de um evento no fim do projeto.
Neste artigo
O custo de descobrir tarde#
Existe uma assimetria brutal no custo de uma falha de segurança, e ela é o argumento econômico mais forte a favor de tratar segurança como parte do desenvolvimento, não como um carimbo no fim. Uma falha de autorização identificada durante o desenho de uma feature custa o tempo de uma conversa e algumas linhas reescritas no diagrama. A mesma falha descoberta em revisão de código custa um comentário no pull request e um commit. Descoberta em teste, custa um ciclo de correção, novo build e nova bateria de verificação. Descoberta em produção — por um cliente, por um pesquisador de segurança ou, no pior caso, por um atacante — custa resposta a incidente, comunicação de crise, possível notificação regulatória, rotação de credenciais, análise forense e a erosão de confiança que nenhuma nota de release recupera. O defeito é o mesmo; o que muda é quantas camadas de trabalho já foram construídas em cima dele antes de alguém perceber.
Esse é o cerne do princípio conhecido como shift left: mover a preocupação com segurança para a esquerda da linha do tempo do projeto, para as fases de requisitos e design, onde a correção ainda é barata e local. Quanto mais à direita — mais perto da entrega e da operação — mais cara e mais arriscada fica a correção, porque o defeito já se entrelaçou com outras decisões, ganhou dependências e, muitas vezes, já está exposto. Prevenir uma falha no design é escrever um parágrafo de requisito ou desenhar uma fronteira de confiança corretamente. Corrigir a mesma falha em produção é acordar um time inteiro no meio da noite. A diferença de ordem de grandeza não é retórica de consultoria: é a razão pela qual segurança precisa deixar de ser um evento pontual e virar uma propriedade contínua do ciclo.
Vale distinguir este texto de um artigo sobre pentest. Teste de invasão é uma fase — importante, mas uma entre várias — que exercita o sistema já construído procurando o que passou. O que este artigo trata é o ciclo inteiro: como cada etapa do desenvolvimento, do primeiro requisito até a resposta a incidente em operação, incorpora segurança por desenho. O pentest confirma; o design previne. Um time que só confia no pentest está pagando o preço da coluna mais cara da tabela de custos, repetidamente.
Segurança em cada fase do SDLC#
A ideia de SDLC seguro é simples de enunciar e difícil de sustentar: cada fase do ciclo de desenvolvimento tem uma atividade de segurança que lhe é própria, e pular qualquer uma delas empurra o custo para a direita. Vamos percorrê-las.
Na fase de requisitos, segurança aparece na forma de requisitos de segurança explícitos e de abuse cases. Enquanto o time de produto descreve o que o usuário legítimo deve conseguir fazer — o caso de uso —, a disciplina de segurança pergunta o que um usuário malicioso não deve conseguir fazer, e o que acontece quando ele tenta. Se o requisito diz "o usuário pode exportar seus dados", o abuse case correspondente pergunta: e se ele tentar exportar os dados de outro usuário mudando um identificador na URL? Requisitos de segurança tornam explícitas expectativas que de outra forma ficariam implícitas e, portanto, não testadas: retenção de logs, força mínima de autenticação, isolamento entre tenants, comportamento sob carga hostil. O que não está escrito como requisito raramente vira código, e o que não vira código nunca vira teste.
Na fase de design entra o threat modeling, que é o coração deste artigo e merece a seção detalhada mais adiante. Por ora, basta a ideia: antes de escrever a primeira linha, o time olha o desenho da solução e pergunta sistematicamente o que pode dar errado. É a intervenção de segurança de maior retorno sobre investimento em todo o ciclo, precisamente porque acontece no ponto mais à esquerda em que já existe algo concreto para analisar.
Na fase de implementação, segurança é padrão de código seguro e revisão. Isso significa parametrizar toda query em vez de concatenar strings, validar e canonicalizar entrada na borda, escapar saída conforme o contexto de renderização, tratar todo erro em vez de engoli-lo silenciosamente, e nunca embutir segredo no código ou no bundle do cliente. Significa também que a revisão de código incorpora um olhar de segurança — não apenas "isso funciona?", mas "isso resiste a entrada hostil?". Revisão por par é barata e pega uma classe inteira de defeitos que ferramenta automatizada não enxerga, porque envolve entender a intenção e o contexto de negócio.
Na fase de teste, entram as ferramentas automatizadas e o pentest. SAST (análise estática) lê o código-fonte procurando padrões perigosos sem executá-lo. DAST (análise dinâmica) ataca a aplicação em execução, como um atacante externo faria, observando respostas. SCA (análise de composição de software) inventaria as dependências de terceiros e cruza com bases de vulnerabilidades conhecidas — porque a maior parte do código que roda em produção hoje não foi escrita pela sua equipe. E o pentest manual, conduzido por gente que pensa como atacante, encontra as cadeias de exploração que nenhuma ferramenta encadeia sozinha.
Na fase de deploy, segurança é configuração. Uma aplicação perfeita implantada com configuração insegura é uma aplicação insegura. Isso cobre gestão de segredos (nunca em texto puro no repositório ou em variável de ambiente prefixada que vaza para o cliente), imagens de container mínimas e sem privilégio de root, sistema de arquivos read-only, headers de segurança configurados, TLS obrigatório, portas administrativas fechadas. A superfície de configuração é onde muitos incidentes reais nascem, porque é a fase que os times tratam com menos rigor.
Na fase de operação, segurança é vigilância contínua: monitoramento e alerta que detectam comportamento anômalo, aplicação disciplinada de patches — porque a dependência segura de hoje é a vulnerabilidade divulgada de amanhã —, e um processo de resposta a incidente ensaiado antes de precisar dele. Segurança não termina no deploy; ela apenas muda de forma, passando de prevenção para detecção e resposta.
O ponto que amarra tudo isso é que nenhuma dessas fases substitui as outras. Pentest não compensa design ruim; monitoramento não compensa código inseguro; um bom design não dispensa a checagem automatizada no CI. Segurança é uma propriedade emergente de fazer a coisa certa em cada etapa, e uma corrente é tão forte quanto seu elo mais fraco.
Threat modeling: pensar como atacante antes de codar#
Threat modeling é o exercício estruturado de, diante de um desenho de sistema, antecipar como ele poderia ser atacado e decidir o que fazer a respeito — tudo isso antes de escrever código. É a materialização do shift left: a análise mais barata possível, feita no artefato mais barato de alterar, que é o diagrama e a decisão de arquitetura. Não requer que o sistema exista; requer apenas que se saiba o que se pretende construir.
A prática se organiza em torno de quatro perguntas, popularizadas por Adam Shostack, que dão a espinha dorsal de qualquer sessão de modelagem. A primeira: no que estamos trabalhando? Aqui o time desenha o sistema — seus componentes, os fluxos de dados entre eles, os repositórios de dados, os atores externos. A segunda: o que pode dar errado? Aqui se enumeram as ameaças, e é onde entra o STRIDE, que veremos em detalhe. A terceira: o que vamos fazer sobre isso? Aqui cada ameaça relevante recebe uma decisão — mitigar, eliminar, transferir ou aceitar conscientemente o risco. A quarta: fizemos um bom trabalho? Aqui se valida o modelo contra o sistema que efetivamente será construído, fechando o laço. As quatro perguntas parecem óbvias, e essa é a força delas: qualquer time consegue respondê-las, e o simples ato de respondê-las em conjunto revela suposições divergentes que de outra forma só apareceriam em produção.
O artefato central da primeira pergunta é o diagrama de fluxo de dados com fronteiras de confiança explícitas. Uma fronteira de confiança é qualquer linha no diagrama onde o nível de confiança nos dados muda: a borda entre a internet e o seu servidor, entre o navegador e a API, entre um microserviço e outro, entre o código da aplicação e o banco de dados. É exatamente sobre essas fronteiras que os ataques acontecem, porque é onde dados de uma zona menos confiável entram em uma zona mais confiável. Desenhar as fronteiras é o passo mais importante do modelo: uma vez que se enxerga onde a confiança muda, as perguntas certas se fazem sozinhas. Todo dado que cruza uma fronteira de confiança precisa ser validado, autenticado e autorizado do lado de dentro — nunca com base na palavra de quem está do lado de fora.
STRIDE, categoria por categoria#
STRIDE é um acrônimo que serve de checklist mnemônico para a segunda pergunta — o que pode dar errado. Cada letra corresponde a uma categoria de ameaça, e cada categoria é o espelho negativo de uma propriedade de segurança que se deseja preservar. A elegância do modelo é que ele transforma a pergunta aberta e intimidante "como isso pode ser atacado?" em seis perguntas específicas e respondíveis, aplicadas a cada elemento e a cada fronteira do diagrama.
Spoofing — falsificação de identidade — é a ameaça de um atacante se passar por outra entidade: outro usuário, outro serviço, outro processo. A propriedade violada é a autenticação. Um exemplo concreto: um endpoint interno entre microserviços que confia em um header X-User-Id enviado pelo chamador, sem verificar quem o enviou. Qualquer serviço comprometido — ou qualquer requisição que consiga alcançar o endpoint — pode alegar ser qualquer usuário. A mitigação correspondente é autenticação forte em ambas as pontas: tokens assinados e verificados por inteiro (assinatura, expiração, emissor, audiência, algoritmo em allowlist), mTLS entre serviços, e o princípio de que identidade sempre vem de uma credencial verificada, nunca de um campo autodeclarado.
Tampering — adulteração — é a ameaça de modificação não autorizada de dados, seja em trânsito, em repouso ou em parâmetros de requisição. A propriedade violada é a integridade. Exemplo: um cliente que envia, no corpo de uma requisição de compra, o preço do produto, e o servidor confia nesse valor. O atacante altera o preço para centavos. Ou, de forma mais sutil, um campo site_id no corpo de um POST que o servidor usa para associar o recurso, permitindo que o atacante crie recursos em um site que não é seu. A mitigação é nunca confiar em dados que cruzaram uma fronteira: recalcular o preço no servidor a partir da fonte autoritativa, derivar o site_id do contexto autenticado em vez do corpo, assinar ou usar HMAC em dados que precisam sobreviver do lado do cliente, e garantir TLS para integridade em trânsito.
Repudiation — repúdio — é a ameaça de um ator negar ter realizado uma ação, na ausência de prova em contrário. A propriedade violada é a não-repudiação, sustentada por log e auditoria. Exemplo: um usuário administrativo executa uma operação destrutiva — apaga dados de um cliente — e depois nega tê-lo feito, e o sistema não tem registro que prove nada. A mitigação é auditoria robusta: logs de eventos sensíveis à segurança, imutáveis ou ao menos protegidos contra adulteração, com identidade do ator, carimbo de tempo absoluto e contexto suficiente para reconstruir o que aconteceu. Log de auditoria não é a mesma coisa que log de aplicação; ele existe para provar quem fez o quê, e precisa ser desenhado com essa intenção.
Information Disclosure — divulgação de informação — é a ameaça de exposição de dados a quem não deveria vê-los. A propriedade violada é a confidencialidade. Exemplos abundam: uma mensagem de erro 500 que ecoa a string do driver do banco de dados, revelando estrutura interna e às vezes fragmentos de query; um endpoint que retorna mais campos do que a interface usa, incluindo dados sensíveis de outros usuários; uma falha de isolamento que permite a um tenant ler dados de outro. A mitigação é redação de erros na borda (o cliente recebe um identificador de correlação, não o stack trace), retorno mínimo de campos, criptografia de dados sensíveis em repouso e em trânsito, e verificação rigorosa de autorização em toda leitura — inclusive nas rotas de busca por identificador, onde o IDOR clássico vive.
Denial of Service — negação de serviço — é a ameaça de tornar o sistema indisponível para usuários legítimos, esgotando algum recurso. A propriedade violada é a disponibilidade. Exemplo: um endpoint que decodifica o corpo da requisição sem limite de tamanho, permitindo que um atacante envie um payload gigante e esgote a memória do processo; ou uma operação cara e sem timeout que pode ser disparada repetidamente até saturar a capacidade. A mitigação é limitar tudo o que pode ser abusado: teto no tamanho do corpo, rate limiting, timeouts em toda operação longa, paginação com limite máximo imposto no servidor, e backpressure com filas limitadas em vez de crescimento ilimitado. Disponibilidade é uma propriedade de segurança tanto quanto confidencialidade, e frequentemente a mais negligenciada no design.
Elevation of Privilege — elevação de privilégio — é a ameaça de um ator obter capacidades além das que lhe foram concedidas. A propriedade violada é a autorização. Exemplo: um usuário comum que acessa uma rota administrativa porque a verificação de papel só existe na interface — o botão fica escondido no frontend, mas a rota do servidor não re-verifica —, ou um usuário de um tenant que consegue executar uma ação em outro tenant porque o gateway resolve permissões a partir de um valor que o próprio cliente controla. A mitigação é autorização decidida sempre no servidor, com política deny-default, papel e tenant derivados de credencial verificada, e verificação em cada camada — no gateway, no serviço e, quando possível, no dado. Esconder um botão é experiência de usuário; não é controle de acesso.
O valor de percorrer STRIDE elemento por elemento e fronteira por fronteira é a exaustividade que ele impõe. Sem o checklist, a análise de ameaças tende a girar em torno das duas ou três categorias que a pessoa que conduz a sessão conhece melhor. Com ele, é difícil esquecer a categoria de repúdio ou de disponibilidade só porque não estavam no topo da cabeça de ninguém. O modelo não garante que se encontre tudo, mas garante que não se ignore uma classe inteira de ameaças por omissão.
DevSecOps: gates automatizados que falham o build#
Threat modeling desenha o alvo; DevSecOps garante que ele não se degrade a cada commit. A ideia central é automatizar verificações de segurança como portões no pipeline de integração contínua, de modo que uma regressão de segurança falhe o build da mesma forma que um teste quebrado falha. Se a verificação é manual e opcional, ela não acontece sob pressão de prazo — e prazo é justamente quando os atalhos perigosos são tentadores.
Os portões típicos incluem SAST rodando a cada push, análise de dependências (SCA) que barra a introdução de biblioteca com vulnerabilidade conhecida, secret scanning que impede que uma chave ou token seja commitado, e lint de segurança que trava padrões proibidos — concatenação em query, uso de gerador aleatório fraco, desativação de verificação de certificado. Cada um desses portões converte uma classe de defeito em uma falha de build determinística e imediata, no momento em que é mais barato corrigir.
O princípio inegociável aqui é: falhar o build em regressão de segurança, e nunca desligar a regra para passar. A tentação, sob prazo, é silenciar o alerta — adicionar um comentário de supressão, baixar o limiar, marcar a regra como aviso em vez de erro. Isso é trocar segurança real por uma barra verde mentirosa. Se uma regra dispara falso positivo com frequência, o caminho é calibrar a regra, não desligá-la. Se a dependência vulnerável não tem atualização, o caminho é uma decisão de risco documentada e revisada, não uma supressão silenciosa que ninguém revisita. O portão só protege enquanto tem dentes; um portão que se abre sob pressão não é um portão.
Cultura: a segurança que ferramenta nenhuma automatiza#
Nenhuma ferramenta compensa uma cultura que trata segurança como problema de outra pessoa. Três práticas culturais sustentam o SDLC seguro no longo prazo.
A primeira são os security champions: em cada time de produto, uma pessoa que não é especialista em segurança em tempo integral, mas que assume o papel de ponte com a disciplina — puxa a sessão de threat modeling, faz a pergunta de segurança na revisão, mantém o time atento. O champion escala o conhecimento de segurança sem exigir um especialista dedicado por time, e cria um interlocutor local que entende tanto o domínio do produto quanto o vocabulário de segurança.
A segunda é uma definição de pronto que inclui segurança. Uma funcionalidade não está pronta porque funciona; está pronta quando funciona e passou pelos requisitos de segurança aplicáveis — entrada validada, autorização verificada, erros tratados, testes de caso hostil escritos, sem segredo no código. Quando segurança faz parte da definição de pronto, ela deixa de ser negociável no calor do sprint e passa a ser condição de entrega, como um teste que precisa passar.
A terceira é o post-mortem sem culpados. Quando um incidente acontece — e vai acontecer —, a análise foca em o que no sistema e no processo permitiu isso, não em quem errou. Cultura de culpa produz ocultação: as pessoas escondem erros para se proteger, e o sistema não aprende. Cultura sem culpa produz honestidade: as pessoas relatam o quase-acidente, o processo melhora, e a mesma falha não se repete. Segurança é um problema de aprendizado organizacional tanto quanto técnico, e organizações que punem a mensagem param de receber mensagens.
Princípios atemporais#
Ferramentas mudam, linguagens mudam, o STRIDE ganhará sucessores. Alguns princípios, porém, atravessam gerações de tecnologia porque descrevem propriedades estruturais de sistemas defensáveis.
Defesa em profundidade: nunca depender de uma única camada de proteção. Se a validação na borda falhar, a autorização no serviço ainda barra; se essa falhar, a restrição no banco ainda limita o dano. Camadas redundantes garantem que uma única falha não seja catastrófica. Privilégio mínimo: cada componente, usuário e processo recebe exatamente as permissões de que precisa para sua função, e nada além. Um serviço que só lê não tem credencial de escrita; um token que só serve a uma operação não abre outras. Secure by default: o estado padrão do sistema é o seguro. A configuração restritiva é a que vem de fábrica; abrir uma permissão é uma decisão explícita, não o contrário. Fail secure: quando algo dá errado, o sistema falha para o estado seguro — nega o acesso, não o concede. Um erro na verificação de autorização deve resultar em negação, jamais em liberação. Minimizar a superfície de ataque: cada endpoint, cada dependência, cada porta aberta, cada campo aceito é uma superfície que precisa ser defendida. Reduzir a superfície — remover o que não se usa, fechar o que não precisa estar aberto, aceitar só os campos previstos — reduz proporcionalmente o que precisa ser protegido e o que pode dar errado.
Esses princípios não são regras a decorar; são lentes para tomar decisões quando nenhuma regra específica se aplica. Diante de uma escolha de design, perguntar "isso falha para o estado seguro?" ou "isso concede mais privilégio do que o necessário?" leva, na maioria das vezes, à decisão certa mesmo em território desconhecido.
O fio que costura tudo isso é a mudança de postura que abre o artigo. Segurança não é uma fase, uma ferramenta ou um pentest ao fim do projeto. É uma propriedade que se constrói decisão por decisão, do primeiro requisito à resposta ao incidente, e que custa pouco quando cultivada cedo e caro quando remendada tarde. O time que internaliza o shift left, que modela ameaças antes de codar, que automatiza seus portões e que trata segurança como parte da definição de pronto não elimina o risco — nenhum time faz isso —, mas paga por ele na coluna mais barata da tabela, todos os dias, em vez de na mais cara, de uma vez só, no pior momento possível.
