Lovable 代码库审计:安全问题、RLS 漏洞和暴露的 API 密钥
让我明确一点:Lovable是一个用于原型设计的令人印象深刻的工具。它从自然语言提示生成工作UI的速度确实非常值得称赞。但"能正常工作的原型"和"生产就绪的应用"之间存在巨大的差距,这个差距充满了大多数非技术创始人甚至不知道应该寻找的安全漏洞。
本文是对我们在多个Lovable代码库审计中发现的内容的技术分析。如果你用Lovable构建了什么东西,并且你正在处理真实用户的钱或数据,你需要阅读本文。
目录
- Lovable实际生成的内容
- API密钥问题
- 行级安全:无声杀手
- 身份验证和授权差距
- 客户端业务逻辑暴露
- Supabase边函数:缺失的部分
- 我们发现的常见漏洞模式
- 如何审计你自己的Lovable项目
- 何时重建vs何时修复
- 常见问题

Lovable实际生成的内容
Lovable生成React应用(通常使用Vite),配合Tailwind CSS和shadcn/ui组件,连接到Supabase后端。技术栈本身是可靠的——我们在Next.js开发工作中定期使用这些相同的工具。问题不在于技术选择。问题在于它们的连接方式。
以下是典型的Lovable项目结构:
src/
├── components/
│ ├── ui/ # shadcn组件
│ ├── Dashboard.tsx
│ ├── Settings.tsx
│ └── ...
├── integrations/
│ └── supabase/
│ ├── client.ts # ← 问题从这里开始
│ └── types.ts
├── pages/
├── hooks/
└── lib/
生成的代码干净易读。我会为Lovable的这一点点赞。但可读性不等于安全性。该架构做出的决定对演示来说是可以的,但对生产来说很危险。
让我们更具体一点。
API密钥问题
我们审计的每个Lovable项目都在客户端代码中有Supabase凭证。现在,在Supabase支持者出现之前:是的,anon密钥被设计为公开的。Supabase的文档明确说明了这一点。anon密钥旨在在客户端代码中使用,安全应该通过行级安全策略强制执行。
但这里变得很丑陋。
在我们审计的六个项目中的三个中,我们发现Supabase的service_role密钥要么:
- 直接硬编码在实用程序文件中
- 存储在提交到GitHub仓库的
.env文件中 - 在可无需身份验证访问的Supabase边函数中引用
service_role密钥绕过所有RLS策略。如果有人获得它,他们对整个数据库具有完全的读/写访问权限。每个表。每一行。每个用户的数据。
// 我们在Lovable项目中发现的实际模式(密钥已编辑)
import { createClient } from '@supabase/supabase-js'
// 这在一个叫lib/admin.ts的文件中
// 从客户端组件导入并使用
const supabaseAdmin = createClient(
'https://xxxxx.supabase.co',
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...' // service_role密钥!
)
这不是由Lovable直接生成的——这是创始人在需要管理功能并要求Lovable"添加管理面板"时添加的。Lovable照办了,由于它没有服务器端vs客户端安全边界的概念,它把密钥放在了方便的地方。
即使只暴露anon密钥(如预期),如果RLS配置不正确,安全模型也会崩溃。这将我们带到大问题。
行级安全:无声杀手
行级安全(RLS)是Supabase用来在数据库级别保护数据的主要机制。正确配置后,它确保用户只能访问他们自己的数据,不管从客户端发出什么API调用。
当我们审计六个Lovable项目时,我们发现了以下内容:
| 项目 | 表总数 | 启用RLS的表 | RLS策略正确的表 | 暴露的敏感数据 |
|---|---|---|---|---|
| SaaS仪表板 | 14 | 6 | 3 | 用户PII、账单数据 |
| 电商应用 | 22 | 10 | 4 | 订单历史、地址 |
| 健康追踪器 | 11 | 4 | 2 | 健康记录、药物 |
| 项目管理器 | 18 | 8 | 5 | 客户端数据、文档 |
| 预订平台 | 16 | 7 | 3 | 联系信息、日程 |
| CRM工具 | 20 | 9 | 4 | 客户数据、备注 |
再读一遍。在健康追踪器应用中——存储了实际药物和健康信息——只有2个表外的11个有正确的RLS策略。拥有anon密钥的人(记住,这是公开的)可以查询每个用户的健康记录。
我们看到的最常见的RLS失败:
完全缺少策略
Lovable经常创建表而不启用RLS。在Supabase中,新表上RLS默认禁用,这意味着任何拥有anon密钥的人都可以读取所有数据。
-- 我们经常发现的:RLS甚至未启用
CREATE TABLE public.user_profiles (
id UUID REFERENCES auth.users,
full_name TEXT,
email TEXT,
phone TEXT,
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- 没有ALTER TABLE ... ENABLE ROW LEVEL SECURITY;
-- 没有定义策略
过度宽松的策略
当Lovable添加RLS时,策略通常太宽泛:
-- 常见的Lovable生成的策略
CREATE POLICY "用户可以查看所有配置文件"
ON public.user_profiles
FOR SELECT
USING (true); -- 这允许任何经过身份验证的用户读取所有配置文件
修复应该是:
-- 应该的样子
CREATE POLICY "用户可以查看自己的配置文件"
ON public.user_profiles
FOR SELECT
USING (auth.uid() = id);
缺少DELETE和UPDATE策略
即使SELECT策略正确,INSERT/UPDATE/DELETE策略也经常缺失或错误。我们发现了任何经过身份验证的用户都可以更新任何其他用户配置文件的情况。

身份验证和授权差距
Lovable相当合理地处理基本身份验证——它使用电子邮件/密码或社交登录设置Supabase Auth,登录/注册流程通常可以正常工作。但身份验证(你是谁?)和授权(你能做什么?)是不同的事情。
授权层几乎总是不完整或不存在。
考虑一个多租户SaaS应用。用户属于组织。他们应该只看到来自他们组织的数据。他们可能有不同的角色(管理员、成员、查看者)。Lovable不生成这些中的任何一个。
我们通常发现:
// Lovable生成的数据获取
const { data: projects } = await supabase
.from('projects')
.select('*')
// 没有organization_id的过滤
// 没有检查用户的角色或权限
修复需要数据库级(RLS)和应用级别的更改。你需要一个memberships表,将用户映射到组织和角色,RLS策略检查成员资格,应用代码适当地过滤。
这是一种难以事后临时抱佛脚的架构工作。它涉及每个查询、每个组件、每个页面。如果你正在使用Lovable构建多租户SaaS,这是最会咬你的东西。
客户端业务逻辑暴露
因为Lovable生成纯客户端React应用,所有业务逻辑都在浏览器中运行。这意味着:
- 定价计算在浏览器DevTools中可见并可操纵
- 功能标志用于不同的订阅层的检查是客户端进行的
- 折扣代码和验证逻辑在JavaScript包中
- API速率限制不存在(没有服务器来强制执行)
我们发现了一个Lovable生成的SaaS,其中定价层检查完全是客户端的:
// 在Lovable项目的组件中找到
const canAccessFeature = (feature: string) => {
const plan = user?.subscription?.plan
if (plan === 'pro') return true
if (plan === 'basic' && BASIC_FEATURES.includes(feature)) return true
return false
}
用户可以简单地在浏览器控制台中修改此函数——或直接调用Supabase API而无需此检查——并在基本计划上访问专业功能。数据库没有策略强制执行基于计划的访问。
这是一个基本的架构问题。业务逻辑需要服务器端组件。无论是Next.js API路由、Supabase边函数还是单独的后端服务——需要有东西验证用户无法篡改的操作。
这正是为什么我们经常为生产SaaS应用推荐Next.js或Astro等框架——它们开箱即用地提供服务器端渲染和API路由。
Supabase边函数:缺失的部分
一些Lovable项目确实对某些操作使用Supabase边函数——通常是Stripe webhook处理或发送电子邮件。但实现经常存在问题:
没有输入验证:边函数接受和处理发送给它们的任何JSON,不验证形状、类型或约束。
配置CORS以允许所有来源:
// Lovable边函数中的常见模式
const corsHeaders = {
'Access-Control-Allow-Origin': '*', // 允许任何网站调用此函数
'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type',
}
没有身份验证检查:函数不验证JWT令牌,所以未经身份验证的用户可以调用它。
Stripe webhook签名未验证:在两个项目中,Stripe webhook处理程序没有验证webhook签名,意味着任何人都可以向端点发送虚假支付事件。
// 我们发现的——没有签名验证
Deno.serve(async (req) => {
const body = await req.json()
// 直接处理事件而不验证它来自Stripe
if (body.type === 'checkout.session.completed') {
// 更新用户的订阅
await supabaseAdmin.from('subscriptions').update({
status: 'active',
plan: 'pro'
}).eq('user_id', body.data.object.metadata.user_id)
}
})
这意味着攻击者可以向webhook URL发送POST请求,其中包含虚假的checkout.session.completed事件,并将任何用户升级到专业计划,完全免费。
我们发现的常见漏洞模式
以下是按严重性和频率排列的最常见问题摘要:
| 漏洞 | 严重性 | 频率(共6个) | 可利用性 |
|---|---|---|---|
| 敏感表上缺少RLS | 严重 | 6/6 | 简单——只需查询表 |
| 过度宽松的RLS策略 | 高 | 6/6 | 使用anon密钥很简单 |
| Service role密钥暴露 | 严重 | 3/6 | 如果找到则微不足道 |
| 没有Stripe webhook验证 | 高 | 4/6 | 中等——需要端点URL |
| 仅客户端授权 | 高 | 6/6 | 使用DevTools很简单 |
| 边函数上没有输入验证 | 中等 | 5/6 | 中等 |
| 边函数上的CORS通配符 | 中等 | 5/6 | 简单 |
| localStorage中的敏感数据 | 中等 | 4/6 | 物理访问或XSS |
| 无速率限制 | 中等 | 6/6 | 微不足道 |
| 不安全的直接对象引用 | 高 | 5/6 | 简单——改变URL中的ID |
如何审计你自己的Lovable项目
如果你用Lovable构建了处理真实用户数据的东西,以下是自己检查这些问题的方法。
步骤1:检查你的Supabase RLS状态
转到你的Supabase仪表板→表编辑器。点击每个表并检查是否启用了RLS。然后转到身份验证→策略并查看每个策略。
或在SQL编辑器中运行此查询:
SELECT
schemaname,
tablename,
rowsecurity
FROM pg_tables
WHERE schemaname = 'public'
ORDER BY tablename;
如果对于任何包含用户数据的表rowsecurity为false,那是一个严重问题。
步骤2:搜索暴露的密钥
在你的代码库中,搜索:
grep -r "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" src/
grep -r "service_role" src/
grep -r "SUPABASE_SERVICE" src/
第一个模式匹配Supabase JWT密钥的开始。如果你在src/目录中的任何地方找到service_role密钥,那是一个立即严重的漏洞。
步骤3:测试RLS策略
创建第二个测试用户账户。以该用户身份登录并尝试访问用户一的数据。检查浏览器的网络标签——你收到不应该收到的数据吗?
你也可以直接用curl测试:
curl 'https://YOUR_PROJECT.supabase.co/rest/v1/user_profiles?select=*' \
-H "apikey: YOUR_ANON_KEY" \
-H "Authorization: Bearer YOUR_ANON_KEY"
如果这在没有身份验证的情况下返回所有用户配置文件,RLS被破坏了。
步骤4:检查边函数
查看每个边函数以查找:
- JWT验证
- 输入验证
- CORS配置
- Webhook签名验证(用于Stripe/支付处理程序)
步骤5:检查客户端包
运行npm run build并检查输出。在构建的JavaScript中搜索API密钥、业务逻辑和定价数据。包中的任何内容对用户可见。
何时重建vs何时修复
这是每个Lovable创始人一旦意识到这些问题存在就会面临的问题。答案取决于几个因素:
修复如果:
- 你的应用有少于10个表
- 你没有复杂的授权模型(无多租户、无角色)
- 核心架构(数据模型、页面结构)是合理的
- 你主要需要添加RLS策略并将一些逻辑移动到服务器端
重建如果:
- 你需要具有角色和权限的多租户架构
- 你的业务逻辑复杂且全是客户端
- 你需要服务器端渲染以用于SEO或性能
- Supabase架构有重大结构问题
- 你处于需要适当基础设施的规模
对于修复,你通常在查看所有表添加RLS策略、将敏感逻辑迁移到边函数或服务器端层、实现适当的输入验证和添加速率限制。根据复杂性,这是一个2-4周的项目,供经验丰富的开发人员。
对于完全重建,我们通常推荐无头架构,具有适当的关注点分离。它前期花费更多,但为你提供了一个可以扩展的基础。查看我们的定价页面了解情况。
如果你不确定哪条路对你来说是正确的,我们很高兴进行快速评估。在我们的联系页面上告诉我们。
常见问题
Lovable对生产应用安全吗? Lovable可以生成一个可靠的起点,但输出在生产就绪前需要大量的安全加固。生成的代码缺少适当的RLS策略、服务器端验证和授权逻辑。把它看作脚手架,而不是成品建筑。在真实用户信任它与他们的数据之前,你肯定需要一个开发人员来审查和保护代码。
Lovable会暴露我的Supabase API密钥吗?
Supabaseanon密钥有意设计为公开——那是设计的一部分,Supabase的安全模型通过RLS解释了它。问题是当Lovable(或你,通过提示)将service_role密钥放在客户端代码中时。anon密钥公开只有在你的RLS策略是万无一失的情况下才安全,在Lovable生成的项目中,它们通常不是。
什么是行级安全,为什么它很重要? 行级安全(RLS)是一个PostgreSQL功能,Supabase使用它来控制用户可以读取、插入、更新或删除哪些行。没有RLS,任何拥有你的公共anon密钥的人都可以查询整个数据库——每个用户的数据,每条私人记录。它是Supabase支持应用中最重要的安全机制,也是Lovable项目中配置最错误的单个东西。
我可以在没有开发人员的情况下自己修复Lovable安全问题吗? 如果你理解SQL和Supabase的RLS策略语法,你可以使用Supabase仪表板自己添加基本RLS策略。但是,正确获取策略——尤其是对于多租户、共享资源或管理员访问等复杂场景——需要经验。错误的策略可能会将用户锁定在他们自己的数据之外,或者让一切暴露。对于超出简单个人项目的任何东西,让专业人士看一下。
我如何检查我的Lovable应用的数据库是否安全?
最快的测试:打开浏览器的DevTools,转到网络标签,并查看你的应用进行的Supabase API调用。复制apikey头值。然后使用curl或Postman直接使用该密钥查询你的表,不使用任何身份验证令牌。如果你得到了其他用户的数据——或者在应该是私人的表上得到的任何数据——你的RLS被破坏了。
AI生成代码一般的最大安全风险是什么? AI代码生成器优化了使事物工作,而不是使事物安全。他们没有你的威胁形势的心理模型。最大的风险是:暴露的秘密、缺少输入验证、过度宽松的访问控制和缺少服务器端安全边界。这些不是Lovable独有的——Cursor、v0、Bolt和其他AI工具中的代码存在类似问题。不同的是Lovable生成人们直接部署到生产的完整应用。
在使用Lovable后我应该从Supabase切换到不同的后端吗? Supabase本身很好。这是一个具有适当安全功能的可靠平台。问题是Lovable如何配置它。你不需要放弃Supabase——你需要正确配置RLS策略,将敏感操作移动到边函数,并添加Lovable跳过的授权层。基础设施很好;配置只需要工作。
修复Lovable生成应用中的安全问题需要多少费用? 对于直接修复——添加RLS策略、保护边函数、移除暴露的密钥、添加基本输入验证——取决于表数和授权模型的复杂性,你看的是大约$3,000-$8,000。完全重建具有适当架构的费用为$15,000-$50,000+,取决于范围。如果你的核心数据模型是合理的,修复路径几乎总是更具成本效益。