AI buildery - Lovable, Bolt, v0, Cursor, Base44 - skróciły czas od pomysłu do działającej aplikacji z tygodni do godzin. Ale ten sam mechanizm który generuje kod szybko, generuje go z przewidywalnymi wzorcami. A przewidywalne wzorce to łatwy cel dla atakujących. Po przeanalizowaniu aplikacji zbudowanych przez AI wyodrębniliśmy 8 błędów które regularnie powtarzają się w projektach tego typu.
Luka #1: Supabase Service Role Key w bundle JavaScript
To najczęstszy i najpoważniejszy błąd. AI generuje kod z SUPABASE_SERVICE_ROLE_KEY jako zmienną środowiskową po stronie klienta (z prefiksem NEXT_PUBLIC_). Service role key omija Row Level Security - ktokolwiek go zdobędzie ma pełny dostęp do całej bazy danych.
Wskazówka - Sprawdź: w DevTools → Sources → bundle.js, wyszukaj 'service_role'. Jeśli znajdziesz - to krytyczna luka wymagająca natychmiastowej naprawy i rotacji klucza.
// ŹLE - klucz widoczny w przeglądarce const supabase = createClient( process.env.NEXT_PUBLIC_SUPABASE_URL, process.env.NEXT_PUBLIC_SUPABASE_SERVICE_ROLE_KEY // ❌ ); // DOBRZE - service role tylko server-side // W Server Action lub API Route: const supabaseAdmin = createClient( process.env.SUPABASE_URL, process.env.SUPABASE_SERVICE_ROLE_KEY // ✅ bez NEXT_PUBLIC_ );
Luka #2: Brak Row Level Security (RLS) w Supabase
AI często tworzy tabele Supabase bez włączania RLS. Efekt: każdy zalogowany użytkownik może odczytać i modyfikować dane innych użytkowników przez Supabase JavaScript client. Tabele bez RLS są równoważne publicznym API bez autoryzacji.
-- Sprawdź które tabele mają wyłączone RLS: SELECT tablename, rowsecurity FROM pg_tables WHERE schemaname = 'public' AND rowsecurity = false; -- Włącz RLS na tabeli: ALTER TABLE orders ENABLE ROW LEVEL SECURITY; -- Podstawowa polityka (user widzi tylko swoje dane): CREATE POLICY "own_rows" ON orders USING (auth.uid() = user_id);
Luka #3: Klucz Stripe Publishable Key vs Secret Key
Stripe publishable key (pk_live_...) jest z definicji publiczny - możesz go umieścić w kodzie klienta. Stripe secret key (sk_live_...) jest WYŁĄCZNIE do użytku server-side. AI buildery czasem mylą te dwa klucze lub umieszczają secret key w client-side kodzie obsługi płatności.
Wskazówka - Jeśli Twój sk_live_ klucz wyciekł - natychmiast zrotuj go w Stripe Dashboard → Developers → API Keys. Sprawdź też Stripe Radar czy nie było nieautoryzowanych operacji.
Luka #4: Brak walidacji autoryzacji w Server Actions
Next.js Server Actions generowane przez AI często pomijają sprawdzenie czy użytkownik jest zalogowany. Efekt: każdy może wywołać dowolną Server Action bezpośrednio przez przeglądarkę lub curl - bez sesji.
// ŹLE - brak autoryzacji
export async function deletePost(postId: string) {
await db.post.delete({ where: { id: postId } }); // ❌
}
// DOBRZE - zawsze sprawdź sesję
export async function deletePost(postId: string) {
const session = await getServerSession();
if (!session?.user) throw new Error('Unauthorized');
// sprawdź też czy post należy do tego usera
await db.post.delete({
where: { id: postId, authorId: session.user.id } // ✅
});
}Luka #5: Brak nagłówków bezpieczeństwa HTTP
Aplikacje z Lovable i Bolt hostowane na Vercel domyślnie nie mają skonfigurowanych nagłówków bezpieczeństwa. Brak Content Security Policy, HSTS i X-Frame-Options to automatycznie niski wynik w każdym audycie.
// next.config.ts - dodaj nagłówki bezpieczeństwa:
const securityHeaders = [
{ key: 'X-Frame-Options', value: 'DENY' },
{ key: 'X-Content-Type-Options', value: 'nosniff' },
{ key: 'Referrer-Policy', value: 'strict-origin-when-cross-origin' },
{ key: 'Strict-Transport-Security',
value: 'max-age=63072000; includeSubDomains; preload' },
];
export default {
headers: async () => [{
source: '/(.*)',
headers: securityHeaders,
}],
};Luka #6: Webhooks Stripe bez weryfikacji podpisu
AI generuje webhook handler który przetwarza zdarzenia Stripe (payment.succeeded, subscription.created) bez weryfikacji że żądanie rzeczywiście pochodzi od Stripe. Atakujący może wysłać fałszywy webhook i zaktywować konto premium bez płatności.
// ŹLE - bez weryfikacji
export async function POST(req: Request) {
const body = await req.json();
if (body.type === 'checkout.session.completed') {
await activatePremium(body.data.object.customer); // ❌
}
}
// DOBRZE - z weryfikacją podpisu Stripe
export async function POST(req: Request) {
const body = await req.text();
const sig = req.headers.get('stripe-signature')!;
const event = stripe.webhooks.constructEvent(
body, sig, process.env.STRIPE_WEBHOOK_SECRET! // ✅
);
if (event.type === 'checkout.session.completed') { ... }
}Luka #7: CORS zbyt permisywny
API routes generowane przez AI często mają ustawiony nagłówek 'Access-Control-Allow-Origin: *'. To umożliwia każdej stronie internetowej na świecie wysyłanie żądań do Twojego API w kontekście zalogowanego użytkownika (cross-site request).
Wskazówka - Bezpieczna konfiguracja CORS: ustaw dokładnie swoje domeny produkcyjne: 'Access-Control-Allow-Origin: https://twojadomena.pl'. Wildcard * jest dopuszczalny wyłącznie dla publicznych, nieuwierzytelnionych API.
Luka #8: Zmienne środowiskowe w logach i error messages
AI często generuje kod który wyświetla szczegółowe komunikaty błędów (stack trace, database URL, klucze) w odpowiedziach API lub zapisuje je do logów. W Vercel i Netlify logi są dostępne dla wszystkich współpracowników - i potencjalnie dla atakujących jeśli interfejs logów nie jest odpowiednio zabezpieczony.
- Nigdy nie loguj zmiennych środowiskowych: console.log('Config:', process.env)
- W produkcji zwracaj generyczne komunikaty błędów: 'Coś poszło nie tak' zamiast stack trace
- Używaj Sentry lub podobnego narzędzia które przesyła błędy bezpiecznie bez ekspozycji w API
- Sprawdź Vercel → Project → Logs czy nie ma wycieków sekretów
Jak automatycznie sprawdzić aplikację AI buildera?
Ręczne sprawdzanie 8 warstw bezpieczeństwa przy każdym deploymencie jest nierealistyczne. Wirtualna Tarcza automatycznie wykrywa użycie Lovable, Bolt i v0 i stosuje dedykowany zestaw testów dla każdej platformy - włącznie ze sprawdzeniem bundle JS, konfiguracji Supabase i nagłówków HTTP.