Skip to main content

ShipSafe

ShipSafe
ShipSafe
Security Guide

Common Vulnerabilities by Platform

Every AI coding tool has patterns it gets wrong. Here are the most common security issues we find in apps built with Lovable, Bolt, Cursor, v0, and Base44 — and how ShipSafe catches them.

Lovable

Lovable generates full-stack apps with Supabase. Its AI often skips Row Level Security and leaks service role keys to the client.

1.1Missing Row Level Security (RLS)

Lovable creates tables via SQL migrations but often omits the ALTER TABLE ... ENABLE ROW LEVEL SECURITY step. Without RLS, anyone with the anon key can read or modify every row.

-- Lovable-generated migration (insecure)
CREATE TABLE documents (
  id uuid DEFAULT gen_random_uuid() PRIMARY KEY,
  user_id uuid REFERENCES auth.users(id),
  content text
);
-- Missing: ALTER TABLE documents ENABLE ROW LEVEL SECURITY;
-- Missing: CREATE POLICY "Users can only see own docs" ...

How ShipSafe catches it: ShipSafe flags every CREATE TABLE without a matching ENABLE ROW LEVEL SECURITY in the same migration.

1.2Service Role Key in Client Code

Lovable sometimes uses the Supabase service role key (which bypasses all security rules) directly in React components instead of the anon key.

// Lovable-generated client code (insecure)
const supabase = createClient(
  process.env.NEXT_PUBLIC_SUPABASE_URL!,
  process.env.NEXT_PUBLIC_SUPABASE_SERVICE_ROLE_KEY! // DANGER
);

How ShipSafe catches it: ShipSafe detects service role key references in non-server files and flags them as critical.

1.3Direct auth.users Table Queries

Instead of creating a public profiles table, Lovable sometimes queries auth.users directly, requiring the service role key and exposing sensitive auth metadata.

// Lovable-generated code (insecure)
const { data } = await supabase
  .from('auth.users')  // Should be 'profiles'
  .select('email, created_at')
  .eq('id', userId);

How ShipSafe catches it: ShipSafe flags direct auth.users queries and suggests creating a profiles table synced via trigger.

Bolt.new

Bolt.new generates full-stack web apps quickly. Its AI sometimes exposes environment variables in the frontend bundle and skips auth checks on API routes.

2.1API Routes Without Authentication

Bolt generates API endpoints that handle data mutations without checking if the user is logged in. Anyone who finds the endpoint URL can call it directly.

// Bolt-generated API route (insecure)
export async function POST(req: Request) {
  const { title, content } = await req.json();
  // No auth check — anyone can create posts
  await db.insert(posts).values({ title, content });
  return Response.json({ success: true });
}

How ShipSafe catches it: ShipSafe flags POST/PUT/DELETE handlers that lack auth middleware, session checks, or token validation.

2.2Secrets in Frontend Environment Variables

Bolt sometimes puts API keys in VITE_ or NEXT_PUBLIC_ variables, making them visible in the browser's JavaScript bundle.

// .env file (insecure)
VITE_OPENAI_API_KEY=sk-proj-abc123...
VITE_STRIPE_SECRET_KEY=sk_live_xyz...
# These are bundled into client-side JS!

How ShipSafe catches it: ShipSafe scans for secret-like values in environment variables prefixed with VITE_, NEXT_PUBLIC_, or REACT_APP_.

2.3Missing Input Validation

Bolt-generated forms often pass user input directly to database queries without validation or sanitization.

// Bolt-generated handler (insecure)
const searchTerm = req.query.q;
const results = await db.query(
  `SELECT * FROM products WHERE name LIKE '%${searchTerm}%'`
);

How ShipSafe catches it: ShipSafe detects string interpolation in SQL queries and flags SQL injection risks.

Cursor

Cursor is an AI-powered IDE that writes code inline. Its completions sometimes invert auth logic or skip ownership checks on data access.

3.1Insecure Direct Object Reference (IDOR)

Cursor-generated code often fetches records by ID from URL params without checking if the logged-in user owns that record.

// Cursor-generated route (insecure)
export async function GET(req: Request, { params }: { params: { id: string } }) {
  // Anyone can access any user's data by changing the ID
  const order = await db.orders.findUnique({
    where: { id: params.id }  // Missing: userId check
  });
  return Response.json(order);
}

How ShipSafe catches it: ShipSafe flags data queries using URL params without a corresponding userId/ownerId filter.

3.2Auth Logic Inversions

When generating middleware or auth checks, Cursor sometimes inverts the condition, allowing unauthenticated users through while blocking authenticated ones.

// Cursor-generated middleware (insecure)
export function middleware(req: NextRequest) {
  const token = req.cookies.get('session');
  if (token) {  // BUG: should be if (!token)
    return NextResponse.redirect('/login');
  }
  return NextResponse.next();
}

How ShipSafe catches it: ShipSafe's AI analysis detects inverted auth conditions in middleware and route guards.

3.3Frontend-Only Role Checks

Cursor generates admin checks in React components but forgets to add the same check on the API route, so anyone can call the admin endpoint directly.

// Cursor-generated component (false security)
{user.role === 'admin' && <AdminPanel />}
// But the /api/admin/* routes have no role check!

How ShipSafe catches it: ShipSafe flags role/permission checks that only appear in frontend files without matching server-side enforcement.

v0

v0 generates React components from text descriptions. Its output sometimes includes XSS-prone patterns and missing security headers.

4.1dangerouslySetInnerHTML with User Content

v0 sometimes uses dangerouslySetInnerHTML to render rich text or markdown, creating XSS vulnerabilities if the content comes from users.

// v0-generated component (insecure)
function Comment({ comment }: { comment: { body: string } }) {
  return (
    <div
      dangerouslySetInnerHTML={{ __html: comment.body }}
    />
  );
}

How ShipSafe catches it: ShipSafe flags every dangerouslySetInnerHTML usage and checks whether the content is sanitized with DOMPurify.

4.2Missing Content Security Policy

v0-generated apps rarely include security headers. Without CSP, any script injected via XSS can run freely in users' browsers.

// next.config.js — no security headers
const nextConfig = {
  // v0 generates this with no headers() config
  reactStrictMode: true,
};

How ShipSafe catches it: ShipSafe checks middleware and config files for CSP, HSTS, X-Frame-Options, and other security headers.

4.3Unsanitized Form Data in API Routes

v0 generates form components that submit data to API routes, but the routes often use the data without validation.

// v0-generated API route (insecure)
export async function POST(req: Request) {
  const formData = await req.json();
  // No validation — formData could contain anything
  await db.insert(contacts).values(formData);
}

How ShipSafe catches it: ShipSafe flags API routes that pass request body directly to database operations without schema validation.

Base44

Base44 is an AI app builder that generates full applications. Its output sometimes includes exposed admin endpoints and weak session handling.

5.1Exposed Admin Endpoints

Base44 generates admin routes that are accessible to any user, without role-based access control.

// Base44-generated route (insecure)
app.get('/api/admin/users', async (req, res) => {
  // No auth check — anyone can list all users
  const users = await db.select().from(usersTable);
  res.json(users);
});

How ShipSafe catches it: ShipSafe flags routes containing 'admin' in the path that lack authentication and role verification.

5.2Default Credentials in Code

Base44 sometimes generates placeholder credentials that make it into production, such as admin/admin or test/password123.

// Base44-generated seed/config (insecure)
const defaultAdmin = {
  username: 'admin',
  password: 'admin123',  // Hardcoded default
  role: 'admin'
};

How ShipSafe catches it: ShipSafe detects common default passwords (admin, password, test123, changeme) hardcoded in non-test files.

5.3Weak Session Handling

Base44 generates session management that stores tokens in localStorage (accessible to XSS) without expiration.

// Base44-generated auth (insecure)
localStorage.setItem('token', response.token);
// No expiration, no httpOnly cookie, XSS can steal it

How ShipSafe catches it: ShipSafe flags sensitive data stored in localStorage/sessionStorage and suggests httpOnly cookies instead.

Scan your AI-generated app now

One command finds every vulnerability listed above — plus 80+ more rules across 12 security categories.

npx @ship-safe/cli scan .
Vulnerabilities by Platform | ShipSafe