docs/es/skills/security-review/SKILL.md
Usar este skill al agregar autenticación, manejar entradas de usuario, trabajar con secretos, crear endpoints de API o implementar funcionalidades de pago/sensibles. Proporciona lista de verificación y patrones de seguridad completos.
npx skillsauth add affaan-m/everything-claude-code security-reviewInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Este skill garantiza que todo el código siga las buenas prácticas de seguridad e identifica vulnerabilidades potenciales.
const apiKey = "sk-proj-xxxxx" // Secreto hardcodeado
const dbPassword = "password123" // En el código fuente
const apiKey = process.env.OPENAI_API_KEY
const dbUrl = process.env.DATABASE_URL
// Verificar que los secretos existen
if (!apiKey) {
throw new Error('OPENAI_API_KEY not configured')
}
.env.local en .gitignoreimport { z } from 'zod'
// Definir esquema de validación
const CreateUserSchema = z.object({
email: z.string().email(),
name: z.string().min(1).max(100),
age: z.number().int().min(0).max(150)
})
// Validar antes de procesar
export async function createUser(input: unknown) {
try {
const validated = CreateUserSchema.parse(input)
return await db.users.create(validated)
} catch (error) {
if (error instanceof z.ZodError) {
return { success: false, errors: error.errors }
}
throw error
}
}
function validateFileUpload(file: File) {
// Verificar tamaño (máximo 5MB)
const maxSize = 5 * 1024 * 1024
if (file.size > maxSize) {
throw new Error('File too large (max 5MB)')
}
// Verificar tipo
const allowedTypes = ['image/jpeg', 'image/png', 'image/gif']
if (!allowedTypes.includes(file.type)) {
throw new Error('Invalid file type')
}
// Verificar extensión
const allowedExtensions = ['.jpg', '.jpeg', '.png', '.gif']
const extension = file.name.toLowerCase().match(/\.[^.]+$/)?.[0]
if (!extension || !allowedExtensions.includes(extension)) {
throw new Error('Invalid file extension')
}
return true
}
// PELIGROSO - Vulnerabilidad de inyección SQL
const query = `SELECT * FROM users WHERE email = '${userEmail}'`
await db.query(query)
// Seguro - consulta parametrizada
const { data } = await supabase
.from('users')
.select('*')
.eq('email', userEmail)
// O con SQL puro
await db.query(
'SELECT * FROM users WHERE email = $1',
[userEmail]
)
// FALLA: INCORRECTO: localStorage (vulnerable a XSS)
localStorage.setItem('token', token)
// PASA: CORRECTO: cookies httpOnly
res.setHeader('Set-Cookie',
`token=${token}; HttpOnly; Secure; SameSite=Strict; Max-Age=3600`)
export async function deleteUser(userId: string, requesterId: string) {
// SIEMPRE verificar la autorización primero
const requester = await db.users.findUnique({
where: { id: requesterId }
})
if (requester.role !== 'admin') {
return NextResponse.json(
{ error: 'Unauthorized' },
{ status: 403 }
)
}
// Proceder con la eliminación
await db.users.delete({ where: { id: userId } })
}
-- Habilitar RLS en todas las tablas
ALTER TABLE users ENABLE ROW LEVEL SECURITY;
-- Los usuarios solo pueden ver sus propios datos
CREATE POLICY "Users view own data"
ON users FOR SELECT
USING (auth.uid() = id);
-- Los usuarios solo pueden actualizar sus propios datos
CREATE POLICY "Users update own data"
ON users FOR UPDATE
USING (auth.uid() = id);
import DOMPurify from 'isomorphic-dompurify'
// SIEMPRE sanitizar HTML proporcionado por el usuario
function renderUserContent(html: string) {
const clean = DOMPurify.sanitize(html, {
ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'p'],
ALLOWED_ATTR: []
})
return <div dangerouslySetInnerHTML={{ __html: clean }} />
}
Comenzar con una política estricta y relajarla solo con un plan de eliminación documentado.
No usar 'unsafe-inline' ni 'unsafe-eval' por defecto; neutralizan gran parte de la
protección de CSP y deben tratarse como deuda de compatibilidad temporal.
// next.config.js
const securityHeaders = [
{
key: 'Content-Security-Policy',
value: `
default-src 'self';
base-uri 'self';
object-src 'none';
frame-ancestors 'none';
script-src 'self';
style-src 'self';
img-src 'self' data: https:;
font-src 'self';
connect-src 'self' https://api.example.com;
`.replace(/\s{2,}/g, ' ').trim()
}
]
import { csrf } from '@/lib/csrf'
export async function POST(request: Request) {
const token = request.headers.get('X-CSRF-Token')
if (!csrf.verify(token)) {
return NextResponse.json(
{ error: 'Invalid CSRF token' },
{ status: 403 }
)
}
// Procesar solicitud
}
res.setHeader('Set-Cookie',
`session=${sessionId}; HttpOnly; Secure; SameSite=Strict`)
import rateLimit from 'express-rate-limit'
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutos
max: 100, // 100 solicitudes por ventana
message: 'Too many requests'
})
// Aplicar a rutas
app.use('/api/', limiter)
// Limitación agresiva para búsquedas
const searchLimiter = rateLimit({
windowMs: 60 * 1000, // 1 minuto
max: 10, // 10 solicitudes por minuto
message: 'Too many search requests'
})
app.use('/api/search', searchLimiter)
// FALLA: INCORRECTO: Registrar datos sensibles
console.log('User login:', { email, password })
console.log('Payment:', { cardNumber, cvv })
// PASA: CORRECTO: Redactar datos sensibles
console.log('User login:', { email, userId })
console.log('Payment:', { last4: card.last4, userId })
// FALLA: INCORRECTO: Exponer detalles internos
catch (error) {
return NextResponse.json(
{ error: error.message, stack: error.stack },
{ status: 500 }
)
}
// PASA: CORRECTO: Mensajes de error genéricos
catch (error) {
console.error('Internal error:', error)
return NextResponse.json(
{ error: 'An error occurred. Please try again.' },
{ status: 500 }
)
}
import { verify } from '@solana/web3.js'
async function verifyWalletOwnership(
publicKey: string,
signature: string,
message: string
) {
try {
const isValid = verify(
Buffer.from(message),
Buffer.from(signature, 'base64'),
Buffer.from(publicKey, 'base64')
)
return isValid
} catch (error) {
return false
}
}
async function verifyTransaction(transaction: Transaction) {
// Verificar destinatario
if (transaction.to !== expectedRecipient) {
throw new Error('Invalid recipient')
}
// Verificar monto
if (transaction.amount > maxAmount) {
throw new Error('Amount exceeds limit')
}
// Verificar que el usuario tiene saldo suficiente
const balance = await getBalance(transaction.from)
if (balance < transaction.amount) {
throw new Error('Insufficient balance')
}
return true
}
# Verificar vulnerabilidades
npm audit
# Corregir problemas reparables automáticamente
npm audit fix
# Actualizar dependencias
npm update
# Verificar paquetes desactualizados
npm outdated
# SIEMPRE hacer commit de los archivos lock
git add package-lock.json
# Usar en CI/CD para builds reproducibles
npm ci # En lugar de npm install
// Probar autenticación
test('requires authentication', async () => {
const response = await fetch('/api/protected')
expect(response.status).toBe(401)
})
// Probar autorización
test('requires admin role', async () => {
const response = await fetch('/api/admin', {
headers: { Authorization: `Bearer ${userToken}` }
})
expect(response.status).toBe(403)
})
// Probar validación de entrada
test('rejects invalid input', async () => {
const response = await fetch('/api/users', {
method: 'POST',
body: JSON.stringify({ email: 'not-an-email' })
})
expect(response.status).toBe(400)
})
// Probar limitación de velocidad
test('enforces rate limits', async () => {
const requests = Array(101).fill(null).map(() =>
fetch('/api/endpoint')
)
const responses = await Promise.all(requests)
const tooManyRequests = responses.filter(r => r.status === 429)
expect(tooManyRequests.length).toBeGreaterThan(0)
})
Antes de CUALQUIER despliegue a producción:
Recuerda: La seguridad no es opcional. Una sola vulnerabilidad puede comprometer toda la plataforma. Ante la duda, optar por el lado de la precaución.
tools
Query live GPU inventory, submit an authenticated Itô fixed-rate RFQ, inspect RFQ or procurement status, revoke device credentials, and run explicitly gated node qualification through the separately installed canonical CLI. Use when a user asks to find H100/H200 capacity, request a fixed compute rate, check Itô compute status, validate GPU nodes, revoke Itô access, or rent or purchase GPU compute and needs the supported boundary explained.
testing
Agent-driven scheduling and publishing of social media posts across 13 platforms via SocialClaw. Use when the user wants to publish to X, LinkedIn, Instagram, Facebook Pages, TikTok, Discord, Telegram, YouTube, Reddit, WordPress, or Pinterest — or when managing campaigns, uploading media, or monitoring post delivery status.
development
LLM APIの使用量のコスト最適化パターン — タスクの複雑さによるモデルルーティング、予算追跡、リトライロジック、プロンプトキャッシング。
tools
Use this skill when retrieving Jira tickets, analyzing requirements, updating ticket status, adding comments, or transitioning issues. Provides Jira API patterns via MCP or direct REST calls.