[{"data":1,"prerenderedAt":1610},["ShallowReactive",2],{"blog-/blog/claude-code-security-ja-estava-disponivel":3},{"id":4,"title":5,"body":6,"date":1596,"description":1597,"extension":1598,"meta":1599,"navigation":512,"path":1600,"seo":1601,"stem":1602,"tags":1603,"__hash__":1609},"blog/blog/claude-code-security-ja-estava-disponivel.md","O Claude Code Security já estava disponível, você só não usava",{"type":7,"value":8,"toc":1560},"minimark",[9,13,16,21,24,32,35,39,46,58,69,72,76,91,94,101,107,110,142,149,156,159,250,253,260,267,347,354,361,364,425,432,435,442,452,456,459,463,474,477,645,652,665,669,679,843,862,866,869,1084,1088,1091,1094,1105,1112,1134,1141,1145,1148,1175,1181,1187,1191,1197,1223,1226,1230,1237,1241,1246,1283,1286,1290,1294,1312,1316,1323,1337,1343,1411,1418,1422,1429,1461,1464,1468,1471,1479,1482,1488,1492,1498,1516,1527,1536,1539,1543,1546,1553,1556],[10,11,12],"p",{},"Semana passada a Anthropic anunciou que vai lançar uma ferramenta de segurança integrada ao Claude Code. O mercado reagiu como se fosse uma revolução. Empresas de segurança perderam bilhões em valor de mercado.",[10,14,15],{},"Mas a verdade é que o Claude Code Security já estava disponível. Você só não usava.",[17,18,20],"h2",{"id":19},"ferramentas-de-segurança-existem-há-décadas","Ferramentas de segurança existem há décadas",[10,22,23],{},"SonarQube, Snyk, Checkmarx, Fortify, OWASP ZAP, Burp Suite, Veracode, Semgrep. A lista é longa. Ferramentas de análise estática, análise dinâmica, scanners de dependência, pentest automatizado. Não falta ferramenta.",[10,25,26,27,31],{},"Todas funcionam da mesma forma: olham o código ",[28,29,30],"strong",{},"de fora",". São scanners. Recebem seu código como input, rodam regras, cospem um relatório. Você lê o PDF, prioriza os findings, abre tickets, e talvez corrija alguns. Meses depois, roda de novo.",[10,33,34],{},"O modelo mental é: código de um lado, segurança do outro. Dois mundos separados que se encontram no CI/CD.",[17,36,38],{"id":37},"a-mudança-real-segurança-de-dentro-do-código","A mudança real: segurança de dentro do código",[10,40,41,42,45],{},"O que muda com o Claude Code não é \"mais uma ferramenta de segurança\". É o ",[28,43,44],{},"lugar"," de onde a segurança acontece.",[10,47,48,49,52,53,57],{},"Quando o Claude Code audita seu código, ele não está rodando regras de regex por cima dos seus arquivos. Ele está ",[28,50,51],{},"lendo"," o código. Entendendo o fluxo de autenticação. Seguindo a cadeia de chamadas do handler até o banco de dados. Verificando se o rate limit usa Redis (distribuído) ou memória (inútil em múltiplas instâncias). Olhando se a projeção do MongoDB está filtrando os campos sensíveis. Checando se o ",[54,55,56],"code",{},"timingSafeEqual"," está sendo usado na comparação de tokens.",[10,59,60,61,64,65,68],{},"Ele entende ",[28,62,63],{},"contexto",". Sabe que aquele ",[54,66,67],{},"findById(req.params.id)"," sem filtro de tenant é um BOLA (Broken Object Level Authorization), o vulnerability #1 do OWASP API Security Top 10. Não porque bateu uma regex, mas porque entendeu que a query não tem escopo de multi-tenancy.",[10,70,71],{},"É a diferença entre um guarda que olha o prédio pelo lado de fora e um que conhece cada sala por dentro.",[17,73,75],{"id":74},"eu-já-fazia-isso-com-skills","Eu já fazia isso. Com skills.",[10,77,78,79,82,83,86,87,90],{},"O Claude Code tem um recurso chamado ",[28,80,81],{},"skills",", arquivos markdown em ",[54,84,85],{},"~/.claude/skills/"," que funcionam como instruções especializadas. Quando você invoca uma skill (tipo ",[54,88,89],{},"/security-scan","), o Claude Code recebe aquele contexto e executa a tarefa com o conhecimento embutido no arquivo.",[10,92,93],{},"Eu criei 6 skills de segurança que transformaram meu Claude Code num auditor completo:",[95,96,98,100],"h3",{"id":97},"security-scan-owasp-soc-2",[54,99,89],{},": OWASP + SOC 2",[10,102,103,104,106],{},"A skill mais usada no dia a dia. Toda vez que termino uma feature, rodo ",[54,105,89],{}," nos arquivos alterados.",[10,108,109],{},"Ela faz:",[111,112,113,120,123,126,129,132],"ol",{},[114,115,116,117],"li",{},"Identifica os arquivos alterados via ",[54,118,119],{},"git diff",[114,121,122],{},"Detecta o framework (Fastify, no meu caso)",[114,124,125],{},"Lê cada arquivo procurando as 7 categorias do OWASP Top 10: Injection, Broken Auth, Sensitive Data Exposure, Broken Access Control, Security Misconfiguration, XSS, Insecure Dependencies",[114,127,128],{},"Cruza com critérios SOC 2: CC6 (Access Control), CC7 (System Operations), PI (Processing Integrity), C (Confidentiality), A (Availability)",[114,130,131],{},"Aplica regras específicas de stack: Node.js/Fastify, MongoDB/Mongoose, Redis, Cryptografia",[114,133,134,137,138,141],{},[28,135,136],{},"Cruza com a documentação de segurança do projeto",": se existe um ",[54,139,140],{},"AUDIT_2026.md"," com vulnerabilidades já corrigidas, verifica se a mudança não reintroduz o problema",[10,143,144,145,148],{},"O relatório sai com severidade (CRITICAL/HIGH/MEDIUM/LOW), localização exata (",[54,146,147],{},"file:line","), categoria OWASP ou critério SOC 2, o risco, e o fix exato.",[95,150,152,155],{"id":151},"security-hardening-auditoria-profunda-de-produção",[54,153,154],{},"/security-hardening",": Auditoria profunda de produção",[10,157,158],{},"Essa é a skill pesada. Vai muito além de SOC 2. Cobre:",[160,161,162,168,174,194,210,220,226,232,238,244],"ul",{},[114,163,164,167],{},[28,165,166],{},"OWASP Top 10 (2025)",": todos os 10 itens com patterns de grep e exemplos de código vulnerável vs seguro",[114,169,170,173],{},[28,171,172],{},"OWASP API Security Top 10",": BOLA, Broken Authentication, Mass Assignment, SSRF, cada um com checklist",[114,175,176,179,180,183,184,183,187,183,190,193],{},[28,177,178],{},"Prevenção de NoSQL Injection",": patterns específicos de MongoDB (",[54,181,182],{},"$gt",", ",[54,185,186],{},"$ne",[54,188,189],{},"$regex",[54,191,192],{},"$where",")",[114,195,196,199,200,183,203,183,206,209],{},[28,197,198],{},"Prototype Pollution",": ",[54,201,202],{},"__proto__",[54,204,205],{},"constructor",[54,207,208],{},"prototype"," rejeitados no input",[114,211,212,215,216,219],{},[28,213,214],{},"Rate Limiting & DDoS",": multi-tier limits, Redis-backed, ",[54,217,218],{},"Retry-After",", Slowloris protection",[114,221,222,225],{},[28,223,224],{},"Zero Trust",": re-validação de sessão, encrypt everything, log every access decision",[114,227,228,231],{},[28,229,230],{},"LGPD/GDPR",": minimização de dados, projeções MongoDB, direito de acesso/exclusão/portabilidade",[114,233,234,237],{},[28,235,236],{},"Secrets Management",": patterns de grep para encontrar secrets hardcoded",[114,239,240,243],{},[28,241,242],{},"Security Headers",": HSTS, CSP, X-Frame-Options, COOP, CORP",[114,245,246,249],{},[28,247,248],{},"Penetration Testing Checklist",": checklist manual para testes trimestrais",[10,251,252],{},"A skill termina com um score de 0 a 100, mapa de superfície de ataque, e roadmap de remediação priorizado.",[95,254,256,259],{"id":255},"soc2-audit-soc-2-type-i-e-type-ii-completo",[54,257,258],{},"/soc2-audit",": SOC 2 Type I e Type II completo",[10,261,262,263,266],{},"Essa cobre os ",[28,264,265],{},"61 critérios"," do Trust Services Criteria (TSC) do AICPA:",[160,268,269,275,281,287,293,299,305,311,317,323,329,335,341],{},[114,270,271,274],{},[28,272,273],{},"CC1",": Ambiente de Controle (integridade, supervisão, estrutura, competência, accountability)",[114,276,277,280],{},[28,278,279],{},"CC2",": Comunicação e Informação (qualidade da informação, comunicação interna/externa)",[114,282,283,286],{},[28,284,285],{},"CC3",": Avaliação de Risco (objetivos, análise de risco, fraude, gestão de mudanças)",[114,288,289,292],{},[28,290,291],{},"CC4",": Monitoramento (APM, health checks, alertas, error tracking)",[114,294,295,298],{},[28,296,297],{},"CC5",": Atividades de Controle (auth middleware, sanitização, WAF, CI/CD)",[114,300,301,304],{},[28,302,303],{},"CC6",": Controles de Acesso (autenticação, registro, remoção, restrições físicas, multi-tenancy)",[114,306,307,310],{},[28,308,309],{},"CC7",": Operações do Sistema (monitoring, anomaly detection, incident response, recovery)",[114,312,313,316],{},[28,314,315],{},"CC8",": Gestão de Mudanças (version control, branch protection, PR reviews, CI)",[114,318,319,322],{},[28,320,321],{},"CC9",": Mitigação de Riscos (vendor risk, webhook signatures, SLA monitoring)",[114,324,325,328],{},[28,326,327],{},"A1",": Disponibilidade (capacity, health checks, backups, DR)",[114,330,331,334],{},[28,332,333],{},"PI1",": Integridade de Processamento (validação, idempotência, reconciliação, error correction)",[114,336,337,340],{},[28,338,339],{},"C1",": Confidencialidade (classificação de dados, disposal)",[114,342,343,346],{},[28,344,345],{},"P1",": Privacidade (notice, consent, collection, retention, access, disclosure, quality, monitoring)",[10,348,349,350,353],{},"O diferencial de Type II: não basta o controle existir no código. A skill verifica se existe ",[28,351,352],{},"evidência de operação contínua",": logs provando que o controle roda consistentemente, enforcement automatizado, monitoramento que pega falhas.",[95,355,357,360],{"id":356},"query-review-review-específico-de-mongodb",[54,358,359],{},"/query-review",": Review específico de MongoDB",[10,362,363],{},"Toda query MongoDB que eu escrevo passa por essa skill. Ela verifica:",[160,365,366,372,378,392,404,412],{},[114,367,368,371],{},[28,369,370],{},"Projeção obrigatória",": nunca retornar documentos inteiros (stores podem ter 50KB+)",[114,373,374,377],{},[28,375,376],{},"Timezone",": datas no MongoDB são UTC, Brasil é UTC-3, meia-noite BRT = 03:00 UTC",[114,379,380,383,384,387,388,391],{},[28,381,382],{},"Índices faltantes",": fields em ",[54,385,386],{},"$match"," depois de ",[54,389,390],{},"$unwind",", sorts em grandes collections",[114,393,394,400,401],{},[28,395,396,399],{},[54,397,398],{},"lean()"," faltando",": queries read-only devem usar ",[54,402,403],{},".lean()",[114,405,406,411],{},[28,407,408,399],{},[54,409,410],{},"await",": Mongoose queries são thenables, sem await = bug silencioso",[114,413,414,199,417,420,421,424],{},[28,415,416],{},"Queries sem limite",[54,418,419],{},"find()"," sem ",[54,422,423],{},"limit()"," em collections grandes",[95,426,428,431],{"id":427},"severity-review-review-com-profundidade-configurável",[54,429,430],{},"/severity-review",": Review com profundidade configurável",[10,433,434],{},"Três modos: Critical Only (pré-merge rápido), Standard (review normal), Thorough (nitpick completo). O dev escolhe a profundidade.",[95,436,438,441],{"id":437},"docs-documentação-automática-de-segurança",[54,439,440],{},"/docs",": Documentação automática de segurança",[10,443,444,445,447,448,451],{},"Depois de qualquer mudança, essa skill detecta o que mudou e atualiza os docs correspondentes, incluindo ",[54,446,140],{}," e ",[54,449,450],{},"SOC2_COMPLIANCE.md"," com contadores, status, e barras de progresso.",[17,453,455],{"id":454},"o-que-isso-produziu-na-prática","O que isso produziu na prática",[10,457,458],{},"Agora vamos aos exemplos. Simulando em códigos legados, o resultado foi:",[95,460,462],{"id":461},"nosql-injection-seu-login-aceita-operadores-do-mongodb","NoSQL Injection: seu login aceita operadores do MongoDB?",[10,464,465,466,469,470,473],{},"Esse é o clássico. Um código de login que faz ",[54,467,468],{},"findOne({ email, password })"," direto no banco sem validar tipo. Se alguém mandar ",[54,471,472],{},"{ \"$gt\": \"\" }"," no campo password, o MongoDB retorna o primeiro documento que corresponde. Login sem senha.",[10,475,476],{},"O que a skill encontra e o que o teste valida:",[478,479,484],"pre",{"className":480,"code":481,"language":482,"meta":483,"style":483},"language-typescript shiki shiki-themes github-light github-dark","// O atacante manda isso no body:\n// { email: { $gt: '' }, password: 'anything' }\n// Se o backend não validar tipo, o MongoDB interpreta $gt como operador\n\nit('rejects $gt operator in email', async () => {\n  const res = await app.inject({\n    method: 'POST',\n    url: '/login',\n    payload: { email: { $gt: '' }, password: 'anything' },\n  })\n  expect(res.statusCode).toBe(400) // Zod rejeita: email tem que ser string\n})\n","typescript","",[54,485,486,495,501,507,514,544,569,581,592,610,616,639],{"__ignoreMap":483},[487,488,491],"span",{"class":489,"line":490},"line",1,[487,492,494],{"class":493},"sJ8bj","// O atacante manda isso no body:\n",[487,496,498],{"class":489,"line":497},2,[487,499,500],{"class":493},"// { email: { $gt: '' }, password: 'anything' }\n",[487,502,504],{"class":489,"line":503},3,[487,505,506],{"class":493},"// Se o backend não validar tipo, o MongoDB interpreta $gt como operador\n",[487,508,510],{"class":489,"line":509},4,[487,511,513],{"emptyLinePlaceholder":512},true,"\n",[487,515,517,521,525,529,531,535,538,541],{"class":489,"line":516},5,[487,518,520],{"class":519},"sScJk","it",[487,522,524],{"class":523},"sVt8B","(",[487,526,528],{"class":527},"sZZnC","'rejects $gt operator in email'",[487,530,183],{"class":523},[487,532,534],{"class":533},"szBVR","async",[487,536,537],{"class":523}," () ",[487,539,540],{"class":533},"=>",[487,542,543],{"class":523}," {\n",[487,545,547,550,554,557,560,563,566],{"class":489,"line":546},6,[487,548,549],{"class":533},"  const",[487,551,553],{"class":552},"sj4cs"," res",[487,555,556],{"class":533}," =",[487,558,559],{"class":533}," await",[487,561,562],{"class":523}," app.",[487,564,565],{"class":519},"inject",[487,567,568],{"class":523},"({\n",[487,570,572,575,578],{"class":489,"line":571},7,[487,573,574],{"class":523},"    method: ",[487,576,577],{"class":527},"'POST'",[487,579,580],{"class":523},",\n",[487,582,584,587,590],{"class":489,"line":583},8,[487,585,586],{"class":523},"    url: ",[487,588,589],{"class":527},"'/login'",[487,591,580],{"class":523},[487,593,595,598,601,604,607],{"class":489,"line":594},9,[487,596,597],{"class":523},"    payload: { email: { $gt: ",[487,599,600],{"class":527},"''",[487,602,603],{"class":523}," }, password: ",[487,605,606],{"class":527},"'anything'",[487,608,609],{"class":523}," },\n",[487,611,613],{"class":489,"line":612},10,[487,614,615],{"class":523},"  })\n",[487,617,619,622,625,628,630,633,636],{"class":489,"line":618},11,[487,620,621],{"class":519},"  expect",[487,623,624],{"class":523},"(res.statusCode).",[487,626,627],{"class":519},"toBe",[487,629,524],{"class":523},[487,631,632],{"class":552},"400",[487,634,635],{"class":523},") ",[487,637,638],{"class":493},"// Zod rejeita: email tem que ser string\n",[487,640,642],{"class":489,"line":641},12,[487,643,644],{"class":523},"})\n",[10,646,647,648,651],{},"O fix é simples: Zod na entrada. ",[54,649,650],{},"z.string().email()"," mata o problema na raiz. Mas sem a skill apontando, quantas APIs em produção hoje aceitam objetos onde deveria ter string?",[10,653,654,655,183,657,183,659,183,662,664],{},"A mesma lógica vale pra ",[54,656,186],{},[54,658,189],{},[54,660,661],{},"$or",[54,663,192],{},". Cada operador é um vetor de ataque. A skill testa todos, em todos os endpoints de autenticação.",[95,666,668],{"id":667},"prototype-pollution-o-json-que-envenena-o-processo-inteiro","Prototype Pollution: o JSON que envenena o processo inteiro",[10,670,671,672,674,675,678],{},"Esse é mais sutil. O atacante manda um JSON com ",[54,673,202],{}," no body. Se o backend usar ",[54,676,677],{},"Object.assign()"," ou spread sem sanitizar, o prototype de todos os objetos do processo é poluído.",[478,680,682],{"className":480,"code":681,"language":482,"meta":483,"style":483},"// O atacante manda JSON raw com __proto__\n// Se o parser não rejeitar, {\"isAdmin\": true} vaza pro Object.prototype\n// E qualquer {} no processo passa a ter isAdmin === true\n\nit('rejects __proto__ field with 400', async () => {\n  const res = await app.inject({\n    method: 'POST',\n    url: '/login',\n    headers: { 'content-type': 'application/json' },\n    payload: '{\"email\":\"real@test.com\",\"password\":\"correct123\",\"__proto__\":{\"isAdmin\":true}}',\n  })\n  expect(res.statusCode).toBe(400)\n  // Verifica que Object.prototype NÃO foi poluído\n  expect(({} as Record\u003Cstring, unknown>).isAdmin).toBeUndefined()\n})\n",[54,683,684,689,694,699,703,722,738,746,754,769,779,783,798,804,838],{"__ignoreMap":483},[487,685,686],{"class":489,"line":490},[487,687,688],{"class":493},"// O atacante manda JSON raw com __proto__\n",[487,690,691],{"class":489,"line":497},[487,692,693],{"class":493},"// Se o parser não rejeitar, {\"isAdmin\": true} vaza pro Object.prototype\n",[487,695,696],{"class":489,"line":503},[487,697,698],{"class":493},"// E qualquer {} no processo passa a ter isAdmin === true\n",[487,700,701],{"class":489,"line":509},[487,702,513],{"emptyLinePlaceholder":512},[487,704,705,707,709,712,714,716,718,720],{"class":489,"line":516},[487,706,520],{"class":519},[487,708,524],{"class":523},[487,710,711],{"class":527},"'rejects __proto__ field with 400'",[487,713,183],{"class":523},[487,715,534],{"class":533},[487,717,537],{"class":523},[487,719,540],{"class":533},[487,721,543],{"class":523},[487,723,724,726,728,730,732,734,736],{"class":489,"line":546},[487,725,549],{"class":533},[487,727,553],{"class":552},[487,729,556],{"class":533},[487,731,559],{"class":533},[487,733,562],{"class":523},[487,735,565],{"class":519},[487,737,568],{"class":523},[487,739,740,742,744],{"class":489,"line":571},[487,741,574],{"class":523},[487,743,577],{"class":527},[487,745,580],{"class":523},[487,747,748,750,752],{"class":489,"line":583},[487,749,586],{"class":523},[487,751,589],{"class":527},[487,753,580],{"class":523},[487,755,756,759,762,764,767],{"class":489,"line":594},[487,757,758],{"class":523},"    headers: { ",[487,760,761],{"class":527},"'content-type'",[487,763,199],{"class":523},[487,765,766],{"class":527},"'application/json'",[487,768,609],{"class":523},[487,770,771,774,777],{"class":489,"line":612},[487,772,773],{"class":523},"    payload: ",[487,775,776],{"class":527},"'{\"email\":\"real@test.com\",\"password\":\"correct123\",\"__proto__\":{\"isAdmin\":true}}'",[487,778,580],{"class":523},[487,780,781],{"class":489,"line":618},[487,782,615],{"class":523},[487,784,785,787,789,791,793,795],{"class":489,"line":641},[487,786,621],{"class":519},[487,788,624],{"class":523},[487,790,627],{"class":519},[487,792,524],{"class":523},[487,794,632],{"class":552},[487,796,797],{"class":523},")\n",[487,799,801],{"class":489,"line":800},13,[487,802,803],{"class":493},"  // Verifica que Object.prototype NÃO foi poluído\n",[487,805,807,809,812,815,818,821,824,826,829,832,835],{"class":489,"line":806},14,[487,808,621],{"class":519},[487,810,811],{"class":523},"(({} ",[487,813,814],{"class":533},"as",[487,816,817],{"class":519}," Record",[487,819,820],{"class":523},"\u003C",[487,822,823],{"class":552},"string",[487,825,183],{"class":523},[487,827,828],{"class":552},"unknown",[487,830,831],{"class":523},">).isAdmin).",[487,833,834],{"class":519},"toBeUndefined",[487,836,837],{"class":523},"()\n",[487,839,841],{"class":489,"line":840},15,[487,842,644],{"class":523},[10,844,845,846,849,850,853,854,857,858,861],{},"A última linha é a mais importante: ela verifica que criar um objeto vazio ",[54,847,848],{},"{}"," e acessar ",[54,851,852],{},".isAdmin"," retorna ",[54,855,856],{},"undefined",". Se retornasse ",[54,859,860],{},"true",", o processo inteiro estaria comprometido.",[95,863,865],{"id":864},"response-leakage-usuário-não-encontrado-vs-senha-incorreta","Response Leakage: \"usuário não encontrado\" vs \"senha incorreta\"",[10,867,868],{},"Se sua API retorna mensagens diferentes pra email inexistente e senha errada, qualquer atacante consegue enumerar quais emails existem no seu sistema. Com uma lista de emails, o ataque de força bruta fica muito mais eficiente.",[478,870,872],{"className":480,"code":871,"language":482,"meta":483,"style":483},"it('error messages do not reveal whether email exists', async () => {\n  const resExists = await app.inject({\n    method: 'POST', url: '/login',\n    payload: { email: 'real@test.com', password: 'wrongpassword' },\n  })\n  const resNotExists = await app.inject({\n    method: 'POST', url: '/login',\n    payload: { email: 'notexist@test.com', password: 'wrongpassword' },\n  })\n  // Status code idêntico\n  expect(resExists.statusCode).toBe(401)\n  expect(resNotExists.statusCode).toBe(401)\n  // Mensagem idêntica: não dá pra saber qual email existe\n  expect(resExists.json().message).toBe(resNotExists.json().message)\n  // Até a messageKey tem que ser igual\n  expect(resExists.json().messageKey).toBe(resNotExists.json().messageKey)\n})\n",[54,873,874,893,910,923,939,943,960,972,985,989,994,1010,1025,1030,1053,1058,1079],{"__ignoreMap":483},[487,875,876,878,880,883,885,887,889,891],{"class":489,"line":490},[487,877,520],{"class":519},[487,879,524],{"class":523},[487,881,882],{"class":527},"'error messages do not reveal whether email exists'",[487,884,183],{"class":523},[487,886,534],{"class":533},[487,888,537],{"class":523},[487,890,540],{"class":533},[487,892,543],{"class":523},[487,894,895,897,900,902,904,906,908],{"class":489,"line":497},[487,896,549],{"class":533},[487,898,899],{"class":552}," resExists",[487,901,556],{"class":533},[487,903,559],{"class":533},[487,905,562],{"class":523},[487,907,565],{"class":519},[487,909,568],{"class":523},[487,911,912,914,916,919,921],{"class":489,"line":503},[487,913,574],{"class":523},[487,915,577],{"class":527},[487,917,918],{"class":523},", url: ",[487,920,589],{"class":527},[487,922,580],{"class":523},[487,924,925,928,931,934,937],{"class":489,"line":509},[487,926,927],{"class":523},"    payload: { email: ",[487,929,930],{"class":527},"'real@test.com'",[487,932,933],{"class":523},", password: ",[487,935,936],{"class":527},"'wrongpassword'",[487,938,609],{"class":523},[487,940,941],{"class":489,"line":516},[487,942,615],{"class":523},[487,944,945,947,950,952,954,956,958],{"class":489,"line":546},[487,946,549],{"class":533},[487,948,949],{"class":552}," resNotExists",[487,951,556],{"class":533},[487,953,559],{"class":533},[487,955,562],{"class":523},[487,957,565],{"class":519},[487,959,568],{"class":523},[487,961,962,964,966,968,970],{"class":489,"line":571},[487,963,574],{"class":523},[487,965,577],{"class":527},[487,967,918],{"class":523},[487,969,589],{"class":527},[487,971,580],{"class":523},[487,973,974,976,979,981,983],{"class":489,"line":583},[487,975,927],{"class":523},[487,977,978],{"class":527},"'notexist@test.com'",[487,980,933],{"class":523},[487,982,936],{"class":527},[487,984,609],{"class":523},[487,986,987],{"class":489,"line":594},[487,988,615],{"class":523},[487,990,991],{"class":489,"line":612},[487,992,993],{"class":493},"  // Status code idêntico\n",[487,995,996,998,1001,1003,1005,1008],{"class":489,"line":618},[487,997,621],{"class":519},[487,999,1000],{"class":523},"(resExists.statusCode).",[487,1002,627],{"class":519},[487,1004,524],{"class":523},[487,1006,1007],{"class":552},"401",[487,1009,797],{"class":523},[487,1011,1012,1014,1017,1019,1021,1023],{"class":489,"line":641},[487,1013,621],{"class":519},[487,1015,1016],{"class":523},"(resNotExists.statusCode).",[487,1018,627],{"class":519},[487,1020,524],{"class":523},[487,1022,1007],{"class":552},[487,1024,797],{"class":523},[487,1026,1027],{"class":489,"line":800},[487,1028,1029],{"class":493},"  // Mensagem idêntica: não dá pra saber qual email existe\n",[487,1031,1032,1034,1037,1040,1043,1045,1048,1050],{"class":489,"line":806},[487,1033,621],{"class":519},[487,1035,1036],{"class":523},"(resExists.",[487,1038,1039],{"class":519},"json",[487,1041,1042],{"class":523},"().message).",[487,1044,627],{"class":519},[487,1046,1047],{"class":523},"(resNotExists.",[487,1049,1039],{"class":519},[487,1051,1052],{"class":523},"().message)\n",[487,1054,1055],{"class":489,"line":840},[487,1056,1057],{"class":493},"  // Até a messageKey tem que ser igual\n",[487,1059,1061,1063,1065,1067,1070,1072,1074,1076],{"class":489,"line":1060},16,[487,1062,621],{"class":519},[487,1064,1036],{"class":523},[487,1066,1039],{"class":519},[487,1068,1069],{"class":523},"().messageKey).",[487,1071,627],{"class":519},[487,1073,1047],{"class":523},[487,1075,1039],{"class":519},[487,1077,1078],{"class":523},"().messageKey)\n",[487,1080,1082],{"class":489,"line":1081},17,[487,1083,644],{"class":523},[95,1085,1087],{"id":1086},"_2fa-bypass-a-vulnerabilidade-que-nenhum-scanner-encontra","2FA Bypass: a vulnerabilidade que nenhum scanner encontra",[10,1089,1090],{},"Essa foi a mais grave que a skill encontrou. É uma vulnerabilidade de lógica de negócio, não de input.",[10,1092,1093],{},"O fluxo de login era:",[111,1095,1096,1099,1102],{},[114,1097,1098],{},"Usuário manda email + password corretos",[114,1100,1101],{},"Se tem 2FA, a API pede o código TOTP",[114,1103,1104],{},"Usuário manda email + password + totp_code",[10,1106,1107,1108,1111],{},"O problema: no passo 1, quando a API confirmava email + password e pedia o TOTP, ela ",[28,1109,1110],{},"resetava o contador de tentativas falhas",". Então o atacante podia:",[111,1113,1114,1120,1126,1131],{},[114,1115,1116,1119],{},[54,1117,1118],{},"{email, password}"," → \"informe o TOTP\", counter = 0",[114,1121,1122,1125],{},[54,1123,1124],{},"{email, password, totp_code: \"000001\"}"," → falhou, counter = 1",[114,1127,1128,1130],{},[54,1129,1118],{}," → \"informe o TOTP\", counter = 0 (reset!)",[114,1132,1133],{},"Repetir até acertar",[10,1135,1136,1137,1140],{},"Com 1 milhão de combinações possíveis no TOTP de 6 dígitos, e o counter sempre voltando a zero, era questão de tempo. Nenhum scanner de regex ou análise estática encontra isso. É preciso ",[28,1138,1139],{},"entender o fluxo"," e perceber que a ordem das operações está errada.",[95,1142,1144],{"id":1143},"mais-exemplos-do-que-a-skill-pega","Mais exemplos do que a skill pega",[10,1146,1147],{},"A suíte completa cobre:",[10,1149,1150,1153,1154,183,1156,183,1158,183,1160,183,1162,1164,1165,183,1167,1170,1171,1174],{},[28,1151,1152],{},"injection.test.ts",": NoSQL injection (",[54,1155,182],{},[54,1157,186],{},[54,1159,189],{},[54,1161,192],{},[54,1163,661],{},"), prototype pollution (",[54,1166,202],{},[54,1168,1169],{},"constructor.prototype","), XSS (script tags, img onerror), command injection (",[54,1172,1173],{},"$(whoami)","), type confusion (number, boolean, array, null onde espera string), null byte injection, header injection, response leakage, oversized payloads, malformed JSON.",[10,1176,1177,1180],{},[28,1178,1179],{},"routes.test.ts",": HTTP verb tampering, path traversal, session fixation, IDOR, mass assignment, 2FA bypass.",[10,1182,1183,1186],{},[28,1184,1185],{},"headers-cors.test.ts",": CORS origin validation, Helmet headers, cookie security, Content-Type enforcement.",[95,1188,1190],{"id":1189},"_38-vulnerabilidades-catalogadas-no-total","38 vulnerabilidades catalogadas no total",[10,1192,1193,1194,1196],{},"Tudo documentado no ",[54,1195,140],{},":",[160,1198,1199,1205,1211,1217],{},[114,1200,1201,1204],{},[28,1202,1203],{},"2 CRITICAL",": password reset link logado em plaintext, 2FA bypass via rate limit reset",[114,1206,1207,1210],{},[28,1208,1209],{},"9 HIGH",": admin token com MD5, session token sem entropia, swagger exposto em produção",[114,1212,1213,1216],{},[28,1214,1215],{},"13 MEDIUM",": AES-CTR sem autenticação, rate limit desabilitado quando Redis cai, CORS regex aceita domínio errado",[114,1218,1219,1222],{},[28,1220,1221],{},"14 LOW",": console.log em produção, backup codes sem timing-safe comparison",[10,1224,1225],{},"Com plano de remediação em 4 fases. Fase 1 (críticos): concluída no mesmo dia. Fase 2 (high): 7 de 8 feitos.",[95,1227,1229],{"id":1228},"_164-testes-de-segurança-no-total","164 testes de segurança no total",[10,1231,1232,1233,1236],{},"Tudo que foi mostrado acima são exemplos reais da suíte. No total, são 164 testes: 75 funcionais + 89 de segurança, distribuídos em 4 arquivos dedicados em ",[54,1234,1235],{},"tests/security/",".",[95,1238,1240],{"id":1239},"soc-2-compliance-checklist-com-157-itens","SOC 2 Compliance Checklist com 157 itens",[10,1242,1243,1244,1196],{},"Rastreamento completo no ",[54,1245,450],{},[160,1247,1248,1255,1262,1269,1276],{},[114,1249,1250,1251,1254],{},"Autenticação: ",[28,1252,1253],{},"100%"," (20/20 itens)",[114,1256,1257,1258,1261],{},"Rate Limiting: ",[28,1259,1260],{},"63%"," (5/8)",[114,1263,1264,1265,1268],{},"Sessões e Tokens: ",[28,1266,1267],{},"60%"," (6/10)",[114,1270,1271,1272,1275],{},"Proteção de Dados: ",[28,1273,1274],{},"50%"," (6/12)",[114,1277,1278,1279,1282],{},"Logging e Auditoria: ",[28,1280,1281],{},"38%"," (6/16)",[10,1284,1285],{},"Com progresso visual e plano de ação de 39 itens priorizados.",[17,1287,1289],{"id":1288},"como-fazer-na-sua-máquina","Como fazer na sua máquina",[95,1291,1293],{"id":1292},"_1-crie-o-diretório-de-skills","1. Crie o diretório de skills",[478,1295,1299],{"className":1296,"code":1297,"language":1298,"meta":483,"style":483},"language-bash shiki shiki-themes github-light github-dark","mkdir -p ~/.claude/skills\n","bash",[54,1300,1301],{"__ignoreMap":483},[487,1302,1303,1306,1309],{"class":489,"line":490},[487,1304,1305],{"class":519},"mkdir",[487,1307,1308],{"class":552}," -p",[487,1310,1311],{"class":527}," ~/.claude/skills\n",[95,1313,1315],{"id":1314},"_2-crie-uma-skill","2. Crie uma skill",[10,1317,1318,1319,1322],{},"Cada skill é uma pasta com um ",[54,1320,1321],{},"skill.md"," dentro:",[478,1324,1326],{"className":1296,"code":1325,"language":1298,"meta":483,"style":483},"mkdir -p ~/.claude/skills/security-scan\n",[54,1327,1328],{"__ignoreMap":483},[487,1329,1330,1332,1334],{"class":489,"line":490},[487,1331,1305],{"class":519},[487,1333,1308],{"class":552},[487,1335,1336],{"class":527}," ~/.claude/skills/security-scan\n",[10,1338,1339,1340,1342],{},"O arquivo ",[54,1341,1321],{}," tem frontmatter YAML + instruções em markdown:",[478,1344,1348],{"className":1345,"code":1346,"language":1347,"meta":483,"style":483},"language-markdown shiki shiki-themes github-light github-dark","---\nname: security-scan\ndescription: Quando usar esta skill\n---\n\n# Security Scan\n\n## Steps\n\n### 1. Identifique os arquivos alterados\n### 2. Leia cada arquivo\n### 3. Verifique contra [categoria]\n### 4. Reporte com severidade + fix\n","markdown",[54,1349,1350,1355,1360,1365,1369,1373,1378,1382,1387,1391,1396,1401,1406],{"__ignoreMap":483},[487,1351,1352],{"class":489,"line":490},[487,1353,1354],{},"---\n",[487,1356,1357],{"class":489,"line":497},[487,1358,1359],{},"name: security-scan\n",[487,1361,1362],{"class":489,"line":503},[487,1363,1364],{},"description: Quando usar esta skill\n",[487,1366,1367],{"class":489,"line":509},[487,1368,1354],{},[487,1370,1371],{"class":489,"line":516},[487,1372,513],{"emptyLinePlaceholder":512},[487,1374,1375],{"class":489,"line":546},[487,1376,1377],{},"# Security Scan\n",[487,1379,1380],{"class":489,"line":571},[487,1381,513],{"emptyLinePlaceholder":512},[487,1383,1384],{"class":489,"line":583},[487,1385,1386],{},"## Steps\n",[487,1388,1389],{"class":489,"line":594},[487,1390,513],{"emptyLinePlaceholder":512},[487,1392,1393],{"class":489,"line":612},[487,1394,1395],{},"### 1. Identifique os arquivos alterados\n",[487,1397,1398],{"class":489,"line":618},[487,1399,1400],{},"### 2. Leia cada arquivo\n",[487,1402,1403],{"class":489,"line":641},[487,1404,1405],{},"### 3. Verifique contra [categoria]\n",[487,1407,1408],{"class":489,"line":800},[487,1409,1410],{},"### 4. Reporte com severidade + fix\n",[10,1412,1413,1414,1417],{},"O segredo está na ",[28,1415,1416],{},"especificidade",". Não escreva \"verifique se tem vulnerabilidades\". Escreva exatamente o que verificar, com exemplos de código vulnerável e seguro, com patterns de grep, com referências a critérios específicos (CC6.3, PI1.1, A01).",[95,1419,1421],{"id":1420},"_3-ensine-o-contexto-do-seu-projeto-no-claudemd","3. Ensine o contexto do seu projeto no CLAUDE.md",[10,1423,1424,1425,1428],{},"No ",[54,1426,1427],{},"CLAUDE.md"," do seu projeto, eu coloquei regras como:",[160,1430,1431,1437,1447,1458],{},[114,1432,1433,1434],{},"Toda alteração de segurança atualiza ",[54,1435,1436],{},"docs/security/AUDIT_2026.md",[114,1438,1439,1440,1443,1444,1446],{},"Todo commit passa pelo skill ",[54,1441,1442],{},"/commit"," que roda ",[54,1445,440],{}," automaticamente",[114,1448,1449,1450,183,1452,183,1454,183,1456],{},"Skills disponíveis: ",[54,1451,89],{},[54,1453,359],{},[54,1455,440],{},[54,1457,1442],{},[114,1459,1460],{},"Stack: Fastify 5 + Zod + Mongoose 8 + ioredis",[10,1462,1463],{},"O Claude Code absorve esse contexto e aplica as skills com conhecimento do projeto.",[95,1465,1467],{"id":1466},"_4-use-no-fluxo-natural-de-desenvolvimento","4. Use no fluxo natural de desenvolvimento",[10,1469,1470],{},"Não é um passo extra. É parte do fluxo:",[478,1472,1477],{"className":1473,"code":1475,"language":1476},[1474],"language-text","1. Implementa feature\n2. /security-scan         ← audita os arquivos alterados\n3. Corrige findings\n4. /query-review           ← verifica queries MongoDB\n5. /commit                 ← que roda /docs automaticamente\n","text",[54,1478,1475],{"__ignoreMap":483},[10,1480,1481],{},"Periodicamente:",[478,1483,1486],{"className":1484,"code":1485,"language":1476},[1474],"/security-hardening        ← auditoria profunda\n/soc2-audit                ← compliance completa\n",[54,1487,1485],{"__ignoreMap":483},[17,1489,1491],{"id":1490},"por-que-isso-importa-mais-que-um-scanner-externo","Por que isso importa mais que um scanner externo",[10,1493,1494,1495,1236],{},"Um scanner externo vê o código como texto. O Claude Code vê o código como ",[28,1496,1497],{},"sistema",[10,1499,1500,1501,1504,1505,1508,1509,1512,1513,1236],{},"Ele sabe que o ",[54,1502,1503],{},"clearFailedAttempts"," na linha 69 do ",[54,1506,1507],{},"auth.service.ts"," é chamado ",[28,1510,1511],{},"antes"," da verificação 2FA, e que isso permite brute-force infinito do TOTP, resetando o counter a cada tentativa. Nenhum scanner de regex encontra isso. É uma vulnerabilidade de ",[28,1514,1515],{},"lógica de negócio",[10,1517,1518,1519,1522,1523,1526],{},"Ele sabe que aquele ",[54,1520,1521],{},"Account.findById(id)"," sem segundo argumento vai retornar o documento inteiro incluindo o hash da senha, o secret do 2FA, e os backup codes. Não porque bateu uma regra \"findById sem projeção\", mas porque ",[28,1524,1525],{},"leu o schema do Mongoose"," e entendeu quais campos existem e quais são sensíveis.",[10,1528,1529,1530,1532,1533,1535],{},"Ele sabe cruzar o finding C2 do ",[54,1531,140],{}," com o teste de regressão no ",[54,1534,1152],{}," e confirmar que o fix foi implementado e testado.",[10,1537,1538],{},"Isso não é scan. É auditoria.",[17,1540,1542],{"id":1541},"a-ferramenta-sempre-esteve-ali","A ferramenta sempre esteve ali",[10,1544,1545],{},"Skills são arquivos markdown. Qualquer dev pode criar. Não precisa esperar a Anthropic lançar uma feature oficial. Não precisa pagar uma plataforma de segurança.",[10,1547,1548,1549,1552],{},"O Claude Code já lê código, já entende contexto, já sabe escrever testes, já conhece OWASP e SOC 2. A única coisa que faltava era alguém dizer pra ele ",[28,1550,1551],{},"como"," auditar, com a especificidade de quem realmente faz segurança.",[10,1554,1555],{},"As empresas de segurança não perderam bilhões porque surgiu mais um scanner. Perderam porque o paradigma mudou: a segurança saiu de fora e foi pra dentro do código. E quando está dentro, não tem como competir com quem entende cada linha.",[1557,1558,1559],"style",{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}",{"title":483,"searchDepth":497,"depth":497,"links":1561},[1562,1563,1564,1578,1588,1594,1595],{"id":19,"depth":497,"text":20},{"id":37,"depth":497,"text":38},{"id":74,"depth":497,"text":75,"children":1565},[1566,1568,1570,1572,1574,1576],{"id":97,"depth":503,"text":1567},"/security-scan: OWASP + SOC 2",{"id":151,"depth":503,"text":1569},"/security-hardening: Auditoria profunda de produção",{"id":255,"depth":503,"text":1571},"/soc2-audit: SOC 2 Type I e Type II completo",{"id":356,"depth":503,"text":1573},"/query-review: Review específico de MongoDB",{"id":427,"depth":503,"text":1575},"/severity-review: Review com profundidade configurável",{"id":437,"depth":503,"text":1577},"/docs: Documentação automática de segurança",{"id":454,"depth":497,"text":455,"children":1579},[1580,1581,1582,1583,1584,1585,1586,1587],{"id":461,"depth":503,"text":462},{"id":667,"depth":503,"text":668},{"id":864,"depth":503,"text":865},{"id":1086,"depth":503,"text":1087},{"id":1143,"depth":503,"text":1144},{"id":1189,"depth":503,"text":1190},{"id":1228,"depth":503,"text":1229},{"id":1239,"depth":503,"text":1240},{"id":1288,"depth":497,"text":1289,"children":1589},[1590,1591,1592,1593],{"id":1292,"depth":503,"text":1293},{"id":1314,"depth":503,"text":1315},{"id":1420,"depth":503,"text":1421},{"id":1466,"depth":503,"text":1467},{"id":1490,"depth":497,"text":1491},{"id":1541,"depth":497,"text":1542},"2026-02-23","Ferramentas de segurança existem há décadas. A diferença real não é a ferramenta. É que agora a auditoria acontece de dentro do código. E eu já fazia isso com skills.","md",{},"/blog/claude-code-security-ja-estava-disponivel",{"title":5,"description":1597},"blog/claude-code-security-ja-estava-disponivel",[1604,1605,1606,1607,1608],"seguranca","claude-code","devtools","soc2","owasp","2GmQLwn87MxJ_SB0vuf0FEJWR67uAQtODT6VNxtkOwY",1772834006036]