Безопасность — OWASP top 5 для вайбкодеров
OWASP — некоммерческая организация которая ведёт top-10 самых частых уязвимостей. Разберём пять которые ИИ совершает регулярно — это закроет 80% реальных рисков пет-проекта.
1. SQL-инъекции
Если ты собираешь SQL-запрос склеиванием строк, типа
(где ZZZ — кавычки), то пользователь введёт что-то вроде
и привет всем данным. Защита: ВСЕГДА параметризованные запросы. В Prisma и других ORM — автоматически:
Если используешь raw SQL — Prisma db.$queryRaw с template literal: переменная подставится как параметр, а не как часть SQL.
2. XSS — Cross-Site Scripting
Пользователь оставил коммент:
Если ты вставишь этот текст в HTML напрямую — скрипт выполнится у каждого, кто откроет страницу. React автоматически экранирует через {userInput} — БЕЗОПАСНО. Опасность только когда используешь
никогда без санитайзера типа DOMPurify. Также: в API-ответах ставь Content-Type: application/json — иначе браузер может «угадать» как HTML и выполнить.
3. CSRF — действия от твоего имени
Пользователь залогинен на твоём сайте, открывает злую страницу со скрытой формой
+ автоотправка. Cookie сессии браузер пошлёт автоматически — действие выполнится от имени пользователя. Защита: sameSite=lax/strict у cookie (NextAuth ставит сам), CSRF-токены для форм, double-submit pattern, и вообще — destructive endpoints не делать GET-методом.
4. Утёкшие секреты
Самый частый баг: запушил .env в гит. Боты сканируют GitHub в реальном времени — секунды от пуша до использования. Защита: .gitignore сразу при создании репо, проверка git status перед коммитом, github secrets scanner (включён по умолчанию). Если случилось — отозвать все ключи в дашбордах сервисов и поменять, удаление файла из истории не помогает.
5. IDOR — доступ к чужому
Endpoint /api/orders/42 — заказ. Если ты не проверяешь что ЭТОТ заказ принадлежит ТЕКУЩЕМУ пользователю — кто угодно прочитает чужие данные просто меняя цифру в URL. Защита: в каждом query —
ИИ ОЧЕНЬ часто забывает про userId — это лучший на свете способ слить данные. Проверяй сам.
// ╔═════════════════════════════════════╗
// ║ 1. SQL-INJECTION ║
// ╚═════════════════════════════════════╝
// ❌ ПЛОХО — конкатенация
const sql = `SELECT * FROM user WHERE name = '${name}'`;
// Если name = "'; DROP TABLE user--" — катастрофа
// ✅ ХОРОШО — параметризованный запрос (Prisma)
const users = await db.user.findMany({ where: { name } });
// ✅ Если raw SQL — template literal с Prisma
const users = await db.$queryRaw`SELECT * FROM "user" WHERE name = ${name}`;
// Prisma экранирует автоматически
// ╔═════════════════════════════════════╗
// ║ 2. XSS ║
// ╚═════════════════════════════════════╝
// ✅ React автоматически экранирует
<div>{userComment}</div> // безопасно — < и > превратятся в < >
// ❌ dangerouslySetInnerHTML без санитайзера
<div dangerouslySetInnerHTML={{__html: userComment}} />
// Если userComment = "<script>steal()</script>" — выполнится
// ✅ С санитайзером
import DOMPurify from 'isomorphic-dompurify';
<div dangerouslySetInnerHTML={{__html: DOMPurify.sanitize(userComment)}} />
// ╔═════════════════════════════════════╗
// ║ 3. CSRF ║
// ╚═════════════════════════════════════╝
// ✅ Cookie с правильными флагами (NextAuth ставит сам)
{
httpOnly: true,
secure: true, // только HTTPS
sameSite: 'lax', // не шлёт cookie с других сайтов на твой
}
// ❌ Destructive операции через GET
// app/api/delete-account/route.ts
export async function GET() { /* удаление */ } // ПЛОХО
// ✅ Через POST/DELETE с проверкой Origin
export async function POST(req: Request) {
const origin = req.headers.get('origin');
if (origin !== 'https://example.com') {
return new Response('Forbidden', { status: 403 });
}
// ... удаление
}
// ╔═════════════════════════════════════╗
// ║ 4. УТЁКШИЕ СЕКРЕТЫ ║
// ╚═════════════════════════════════════╝
// ❌ Хардкод в коде
const API_KEY = 'sk-proj-abc123...';
// ✅ В .env, .env в .gitignore
const API_KEY = process.env.OPENAI_API_KEY;
// ✅ Перед каждым commit:
// git diff --cached | grep -i "secret\|api_key\|password"
// (или git-secrets / pre-commit hook)
// ❌ Лог секрета
console.log('Got user data:', user); // если в user есть passwordHash — логи в Sentry это ловят
// ✅ Явно убирай чувствительные поля
console.log('User:', { id: user.id, email: user.email });
// ╔═════════════════════════════════════╗
// ║ 5. IDOR — доступ к чужому ║
// ╚═════════════════════════════════════╝
// ❌ ПЛОХО — нет проверки владельца
// app/api/orders/[id]/route.ts
export async function GET(req: Request, { params }) {
const order = await db.order.findUnique({ where: { id: params.id } });
return Response.json(order);
}
// Любой залогиненный юзер видит ЛЮБОЙ заказ просто меняя URL
// ✅ ХОРОШО — проверяем что заказ принадлежит этому юзеру
export async function GET(req: Request, { params }) {
const session = await getServerSession(authOptions);
if (!session) return new Response('Unauthorized', { status: 401 });
const order = await db.order.findUnique({
where: {
id: params.id,
userId: session.user.id, // ← КРИТИЧНО
},
});
if (!order) return new Response('Not Found', { status: 404 });
return Response.json(order);
}
// ✅ Ещё лучше — middleware который автоматом фильтрует по userId
// (это уже архитектура — Row-level security в Postgres, например)Антипаттерны и правильные варианты для каждой уязвимости