FixVibe

// docs / security guides / hardening

Как защитить приложение, построенное с помощью AI-инструментов кодирования

Пошаговое руководство по усилению защиты приложений, созданных вами с помощью Cursor, Claude Code, Lovable, Bolt, v0, Replit или Windsurf. Четыре этапа: понять, почему приложения, созданные AI-, выходят из строя по-разному, провести немедленный аудит кодовой базы, усилить защиту во время развертывания, а затем продолжить мониторинг. Умный, повествовательный, с реальными фрагментами, которые можно копировать.

Почему приложения, созданные AI-, выходят из строя по-разному

Приложения с кодом Vibe могут быть безопасными. Им нужен дополнительный проход аудита, потому что виды сбоев являются структурными, а не небрежными:

  • Cursor inlines hardcoded keys. Вы просите Cursor «исправить ошибку аутентификации», и он вставляет пример Supabase, в котором предполагается клиент с ролью службы. Ключ оказывается в верхней части компонента страницы. И анонимный клиент, и клиент службы сосуществуют; оба отправляются.
  • Claude Code defaults to permissive CORS. Сгенерированные обработчики Express/Fastify поставляются с cors({ origin: '*' }), потому что это самый быстрый способ получить работающую предварительную версию. Промежуточное программное обеспечение никогда не проходит второй проход.
  • Lovable and v0 skip the rules file. Проекты, поддерживаемые Firestore, генерируют модель данных, но редко затрагивают firestore.rules. Правила тестового режима истекают автоматически и блокируют базу данных без предупреждения пользователя.
  • Bolt skips RLS migrations. Bolt генерирует схему Supabase и поверхность CRUD, использующую ключ anon. ENABLE ROW LEVEL SECURITY никогда не участвует в миграции. Анонимные пользователи могут читать и писать любую строку.
  • Windsurf trusts unsigned IDs. Сгенерированный GET /api/items/[id] считывает параметр и запрашивает Postgres без проверки владения. Паттерн настолько распространен, что активный зонд active.idor-walking обнаруживает его за одно сканирование.

Немедленный аудит: просмотрите свою кодовую базу на наличие шаблонов риска

Прежде чем что-либо укреплять, найдите то, что уже сломано. Каждая из этих операций занимает менее минуты:

Секреты и ключи провайдера

bash
grep -RIn 'NEXT_PUBLIC_SUPABASE_SERVICE' src/
grep -RIn 'sk_live_\|pk_live_\|STRIPE_SECRET' src/
grep -RIn 'sk-ant-\|^sk-' src/  # Anthropic / OpenAI
grep -RIn 'AIza\|AKIA' src/        # Google / AWS
grep -RIn 'eyJh[A-Za-z0-9_-]\{20,\}' src/  # JWT-shaped strings

Любое попадание требует удаления плюс ротации клавиш. Панели Provider: Supabase → Настройки → API, Stripe → Разработчики → клавиши API, консоль Anthropic/OpenAI.

Контроль доступа к базе данных

bash
# Supabase migrations
grep -RIn 'CREATE TABLE public\.' supabase/migrations/
grep -RIn 'ENABLE ROW LEVEL SECURITY\|FORCE ROW LEVEL SECURITY' supabase/migrations/

# Firebase / Firestore
cat firestore.rules  # confirm no `if true;` matches

Каждому CREATE TABLE public.* требуется соответствующий ENABLE ROW LEVEL SECURITY и хотя бы одна политика. Правила Firestore должны ограничивать чтение request.auth.uid.

Аутентификация и обработка сеанса

bash
grep -RIn 'getSession()' src/   # should be getUser() server-side
grep -RIn 'localStorage\.\(set\|get\)Item.*token' src/
grep -RIn 'jwt.verify.*\(noVerify\|skipVerify\)' src/

Маршруты, отображаемые сервером, должны использовать supabase.auth.getUser() — он проверяется на бэкэнде. getSession() считывает непроверенный файл cookie. Токены в localStorage доступны любому скрипту, который выполняется на странице.

Заголовки и промежуточное программное обеспечение

bash
# Confirm middleware location for src/ layouts
ls src/middleware.ts middleware.ts 2>&1

# Look for CSP and security headers
grep -RIn 'Content-Security-Policy\|Strict-Transport-Security' src/

При раскладке src/ подхватывается только src/middleware.ts. Если ваш файл промежуточного программного обеспечения находится в корне проекта, Next.js молча игнорирует его, и ваша логика CSP / auth-refresh никогда не запускается.

Усиление во время развертывания

Как только исходный код будет очищен, заблокируйте путь приложения к рабочей версии.

Шаг 1. Разделение сред

Vercel: три среды — Production (ваш домен продукта), Предварительная версия (PR / промежуточные развертывания), Разработка (локальная). Каждый получает свой собственный набор env-var. Живые ключи Stripe / Anthropic / Supabase никогда не доходят до предварительного просмотра; Ключи предварительного просмотра никогда не достигают Production. Ветки автоматически переходят в режим предварительного просмотра; слияние с main развертывается с Production.

Шаг 2: Строгое CSP через промежуточное программное обеспечение

Сгенерируйте одноразовый номер для каждого запроса, а затем внедрите его в Content-Security-Policy. Next.js автоматически применяет nonce к своим собственным тегам сценария, когда вы устанавливаете заголовок запроса x-nonce.

ts
// src/middleware.ts
import { NextResponse, type NextRequest } from 'next/server';

export function middleware(request: NextRequest) {
  const nonce = crypto.randomUUID().replace(/-/g, '');
  const csp = [
    `script-src 'nonce-${nonce}' 'strict-dynamic'`,
    `style-src 'self' 'unsafe-inline'`,
    `img-src 'self' data: https:`,
    `connect-src 'self' https://*.supabase.co`,
    `object-src 'none'`,
    `base-uri 'self'`,
    `frame-ancestors 'none'`,
  ].join('; ');

  const requestHeaders = new Headers(request.headers);
  requestHeaders.set('x-nonce', nonce);

  const response = NextResponse.next({ request: { headers: requestHeaders } });
  response.headers.set('Content-Security-Policy', csp);
  response.headers.set('X-Content-Type-Options', 'nosniff');
  response.headers.set('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
  return response;
}

export const config = {
  matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'],
};

Шаг 3. Принудительно установите RLS на каждой общедоступной таблице.

RLS не включен по умолчанию и не применяется для владельцев таблиц, если вы не FORCE его. Соедините каждую таблицу с явными политиками для каждой роли.

sql
-- supabase/migrations/XXXX_rls.sql
alter table public.profiles enable row level security;
alter table public.profiles force row level security;

create policy "profiles: read own"
  on public.profiles for select
  using (auth.uid() = id);

create policy "profiles: update own"
  on public.profiles for update
  using (auth.uid() = id)
  with check (auth.uid() = id);

Шаг 4. Проверка подлинности только на сервере на каждом маршруте API

Каждый маршрут API, изменяющий состояние, проверяет серверную сторону вызывающего объекта с помощью supabase.auth.getUser(). Пользовательский объект становится источником истины для user_id — никогда не доверяйте его установку телу запроса.

ts
// src/app/api/items/route.ts
import { NextResponse, type NextRequest } from 'next/server';
import { createClient } from '@/lib/supabase/server';

export async function POST(request: NextRequest) {
  const supabase = await createClient();
  const { data: { user } } = await supabase.auth.getUser();
  if (!user) return NextResponse.json({ error: 'unauthorized' }, { status: 401 });

  const body = await request.json();
  const { data, error } = await supabase
    .from('items')
    .insert({ ...body, user_id: user.id })  // server-supplied, not from body
    .select()
    .single();

  if (error) return NextResponse.json({ error: error.message }, { status: 400 });
  return NextResponse.json(data);
}

Шаг 5. Обратное проксирование вашей аналитики

Proxying аналитика через ваш собственный домен позволяет избежать блокировки рекламы и позволяет вашему CSP connect-src 'self' оставаться узким. Тот же шаблон работает для PostHog, Plausible, Umami, пользовательских приемников событий.

ts
// src/app/api/posthog/[...path]/route.ts
import { type NextRequest } from 'next/server';

const UPSTREAM = 'https://us.i.posthog.com';

export async function POST(req: NextRequest, { params }: { params: Promise<{ path: string[] }> }) {
  const { path } = await params;
  const url = `${UPSTREAM}/${path.join('/')}`;
  return fetch(url, {
    method: 'POST',
    headers: { 'content-type': req.headers.get('content-type') ?? 'application/json' },
    body: await req.text(),
  });
}

Шаг 6. Защита открытого перенаправления при возврате после аутентификации

Потоки входа/регистрации обычно принимают параметр запроса next. Отклоняйте все, что не является путем к тому же сайту — начните с / и никогда не // (относительно протокола, отправляет пользователей за пределы сайта).

ts
function safeNext(raw: string | null): string {
  if (!raw) return '/dashboard';
  if (!raw.startsWith('/') || raw.startsWith('//')) return '/dashboard';
  return raw;
}

Продолжается: мониторинг и повторное сканирование

Дрифт происходит при каждом развертывании. Относитесь к безопасности как к циклу, а не как к контрольному списку, который вы выполняете.

Подтвердите свой рабочий домен

Dashboard → Domains → добавьте свой производственный домен → DNS TXT или HTTP- проверка файла (одноэтапная). После проверки активные сканирования становятся доступными, и можно включить запланированное повторное сканирование.

Запланируйте пассивное повторное сканирование

Ежедневно Hobby, 3 часа Pro, ежечасно Unlimited. Каждый запуск отправляет вам электронное письмо, если появляется новое открытие, и запускает веб-перехватчик scan.completed, если вы подписались.

bash
# Or from CI, via the REST API:
curl -X POST https://fixvibe.app/api/v1/scans \
  -H "authorization: Bearer $FIXVIBE_TOKEN" \
  -H "content-type: application/json" \
  -d '{"target":"https://your-app.com"}'

Включить API-активное сканирование (необязательно)

Если вы хотите автоматическое активное зондирование (SQLi / XSS / IDOR ходьба / и т. д.), включите его для каждого домена в Личном кабинете → Домены → API активный. Разрешение является долгосрочным, сроком действия 90 дней и возможностью немедленного отзыва. Выполните сопряжение с веб-перехватчиком scan.active_api.first_used, чтобы первое автоматическое активное сканирование после включения достигло вашего оповещения.

Включите полученные результаты в свой рабочий процесс AI

Mint an API token at Account → API tokens, then configure the MCP server (/docs/mcp) in Claude Desktop / Cursor / Continue. Ask your agent: "Run a scan on staging and show me the highest-severity findings." The agent calls FixVibe, fetches the report, and renders categorized remediation guidance so code/config fixes become prompts and DNS/provider/manual fixes become operator steps.

Обнаружение угроз в реальном времени (Unlimited)

В журнале прозрачности сертификатов выявляются новые TLS сертификаты, выданные для вашего домена. DNS-diff-записи фиксируют несанкционированные изменения. Мониторинг секретности JS-bundle срабатывает в тот момент, когда новый ключ достигает отправленного пакета. Каналы Threat-intel (Spamhaus, URLhaus) сообщают о вашем домене, если он есть в списке.

Реальные модели сбоев и их исправления

Пять шаблонов, полученных при сканировании тысяч приложений, созданных AI-, каждый из которых имеет фактическое исправление:

  1. Ключ роли службы в клиентском компоненте

    Symptom: baas.supabase-service-key находка на производстве URL. Cause: автозаполнение Cursor, вставленное createClient(URL, SERVICE_ROLE_KEY) в компонент React. Fix: переместите клиент службы в src/lib/supabase/service.ts с import 'server-only' вверху; создайте параллельный src/lib/supabase/client.ts, используя ключ anon для использования на стороне клиента; поверните ключ сервисной роли через Supabase Studio.

  2. Правила Firestore остались в тестовом режиме

    Symptom: baas.firebase-rules вывод высокой степени тяжести. Cause: сгенерированные правила читаются как allow read, write: if request.time < timestamp.date(2026, 6, 1); — ограниченное по времени «разрешить все». Fix: распространите каждое правило на аутентифицированного пользователя — match /users/{userId}/posts/{postId} { allow read, write: if request.auth.uid == userId; } — и повторно разверните firebase deploy --only firestore:rules.

  3. Разрешительное CORS выживание в производстве

    Symptom: active.cors высокая степень серьезности. Cause: создал промежуточное программное обеспечение Express: app.use(cors({ origin: '*' })). Fix: внесите в список разрешенных источник вашего интерфейса: app.use(cors({ origin: ['https://your-app.com'], credentials: true })). Для маршрутов Next.js API укажите Access-Control-Allow-Origin явно в ответе.

  4. RLS включено, но не принудительно

    Symptom: active baas.supabase-rls сообщает, что роль анона может писать в общедоступную таблицу, даже если RLS включена на панели управления. Cause: ENABLE без FORCE оставляет владельца таблицы освобожденным — и миграция выполняется от имени владельца. Fix: добавьте alter table public.items force row level security; к миграции. Повторное развертывание.

  5. Неподписанные IDOR-проходимые идентификаторы

    Symptom: active.idor-walking сообщает, что анонимный пользователь может читать /api/items/1, /api/items/2, ... среди арендаторов. Cause: обработчик API доверяет параметру пути и запрашивает Postgres без предиката владения. Fix: добавляйте .eq('user_id', user.id) в каждый запрос на чтение или переходите к подписанным URL-адресам/UUID, указанным в области /api/users/[uid]/items/[id].

Цикл безопасности с помощью vibe-кода

Целью не является идеальная безопасность; это устраняет низко висящие плоды, которые инструменты AI постоянно упускают из виду, поэтому вы можете продолжать доставку быстро.

  1. Generate fast — используйте Cursor, Claude Code, Lovable, Bolt. В этом вся суть.
  2. Audit immediately — запустите указанный выше набор grep, проверьте RLS, проверьте CSP, просмотрите границу аутентификации.
  3. Harden at deploy — промежуточное программное обеспечение, разделение среды, CSP nonce, HSTS, проверка подлинности только на сервере.
  4. Monitor — FixVibe пассивно ежедневно, активно еженедельно на проверенном домене, вебхуки на Slack, обнаружение угроз на Unlimited.
  5. Fix fast — use FixVibe coding-agent prompts for code/config findings and operator steps for DNS, provider, secret-rotation, or manual-review findings. Re-deploy, re-scan, close the loop.

Следующие шаги

Концептуальную основу по сравнению DAST и SAST и почему приложениям, созданным AI-, необходимо собственное сканирование, читайте в AI-generated code security scanning. Краткую справку по аудиту перед отправкой см. в vibe coding security checklist.

// scan your app

Хватит читать. Найдите бреши в своём приложении.

Добавьте URL — FixVibe выполнит все пассивные проверки из этого руководства, а также более 200 других менее чем за минуту. Free, ни установки, ни карты.

  • Free уровень — 3 скана/месяц, без карты.
  • Пассивное сканирование любых URL — проверка домена не требуется.
  • Настроен на Cursor, Claude Code, Lovable, Bolt, v0, Replit.
  • Coding-agent prompts for code/config findings, plus operator steps for DNS/provider fixes.
Как защитить приложение, построенное с помощью AI-инструментов кодирования — Docs · FixVibe