14 KiB
14 KiB
COPPA 儿童保护与数据安全措施实施方案
文档状态: 草案
创建日期: 2026-04-17
关联文档: docs/discussions/legal-compliance-us.md 第七点、第八点
一、背景与法律要求
1.1 COPPA(儿童在线隐私保护法)要求
针对 13 岁以下用户,COPPA 要求:
- 年龄验证:必须采取合理措施验证用户年龄
- 禁止收集:不得收集 13 岁以下儿童个人信息
- 隐私政策披露:隐私政策必须明确说明不面向 13 岁以下用户
1.2 CCPA/CPRA 数据安全要求
针对数据安全措施,隐私政策声明的安全措施必须真实实施:
- 加密措施:AES-256 数据加密、TLS 1.3 传输加密
- 访问控制:基于角色的访问控制(RBAC)
- 监控审计:审计日志
- 泄露响应:72 小时内通知机制
二、当前系统现状分析
2.1 用户认证流程
现有实现:
- 认证方式:邮箱 + OTP(验证码)登录
- 认证提供方:Supabase Auth
- 用户表:
auth.users(Supabase 内置) - 扩展表:
public.profiles
缺失项:
- ❌ 无年龄验证机制
2.2 数据安全现状
已有措施:
- ✅ Supabase Auth 内置 JWT 认证(HS256 签名)
- ✅ 数据库 RLS(Row Level Security)策略 -
anon/authenticated角色被禁止访问业务表 - ✅ 日志敏感字段过滤(
core/config/settings.py的log_sensitive_fields) - ✅ 软删除机制(
SoftDeleteMixin) - ✅ 环境变量管理敏感配置
- ✅ Supabase 托管服务默认启用 HTTPS + 数据库加密
Supabase 安全特性:
- 传输加密:所有 API 请求强制 HTTPS
- 静态数据加密:PostgreSQL 透明数据加密(TDE)
- 密码哈希:bcrypt 自动处理
- JWT 签名:HS256 算法
- RLS 策略:细粒度行级访问控制
三、第七点:COPPA 儿童保护实施方案
3.1 年龄验证机制
重要澄清:Apple 没有直接的年龄验证 API
Apple 不提供可以获取用户具体年龄的 API。Apple 的年龄保护机制是通过以下方式实现的:
方案一:App Store 年龄分级(推荐 - 主要防护)
原理:在 App Store Connect 中设置年龄分级,App Store 会自动阻止不符合年龄要求的用户下载。
实施步骤:
- 登录 App Store Connect
- 选择应用 → "App 信息" → "年龄分级"
- 填写年龄分级问卷
- 设置年龄分级为 12+(或更高)
年龄分级说明:
- 4+:无 objectionable 内容
- 9+:可能包含轻微的卡通暴力/幻想暴力
- 12+:可能包含轻微的现实暴力/偶尔粗俗幽默/轻微模拟赌博
- 17+:可能包含频繁/强烈的现实暴力/成人内容
设置 12+ 的效果:
- App Store 会阻止 12 岁以下用户下载应用
- 家长控制功能会根据年龄分级限制儿童访问
优点:
- ✅ 零成本,无需开发
- ✅ Apple 系统级保护
- ✅ 自动生效
缺点:
- ⚠️ 仅防止下载,不能防止借用他人设备/账号
- ⚠️ 用户可能谎报年龄注册 Apple ID
方案二:应用内年龄声明(推荐 - 辅助防护)
原理:在注册流程中添加年龄确认 checkbox 或弹窗。
实施步骤:
- 前端修改:
// apps/lib/features/auth/presentation/screens/login_screen.dart
// 在登录界面添加年龄确认 checkbox
Row(
children: [
Checkbox(
value: _ageConfirmed,
onChanged: (value) {
setState(() {
_ageConfirmed = value ?? false;
});
},
),
Expanded(
child: Text(
l10n.ageConfirmationText, // "我已年满 13 岁"
style: Theme.of(context).textTheme.bodySmall,
),
),
],
),
- 后端验证:
# backend/src/v1/auth/schemas.py
class EmailSessionCreateRequest(BaseModel):
model_config = ConfigDict(extra="forbid")
email: str = Field(pattern=SUPABASE_EMAIL_PATTERN)
token: str = Field(min_length=6, max_length=6)
age_confirmed: bool = Field(
default=False,
description="用户确认年满 13 岁"
)
# backend/src/v1/auth/service.py
async def create_email_session(
self, request: EmailSessionCreateRequest
) -> SessionResponse:
if not request.age_confirmed:
raise AgeConfirmationRequiredError(
detail="User must confirm age 13 or older to register"
)
return await self._gateway.create_email_session(request)
- 数据库记录:
-- 在 profiles 表添加年龄确认字段
ALTER TABLE profiles
ADD COLUMN age_confirmed_at timestamptz,
ADD COLUMN age_confirmation_method varchar(20) DEFAULT 'self_declaration';
- 本地化文本:
// apps/lib/l10n/app_zh.arb
{
"ageConfirmationText": "我确认已年满 13 岁,或已获得家长/监护人同意使用本应用",
"@ageConfirmationText": {
"description": "年龄确认声明文本"
}
}
// apps/lib/l10n/app_en.arb
{
"ageConfirmationText": "I confirm that I am 13 years of age or older, or have parental/guardian consent to use this app"
}
优点:
- ✅ 实现简单
- ✅ 满足 COPPA 最低合规要求
- ✅ 记录用户确认时间和方式
缺点:
- ⚠️ 依赖用户自我声明
- ⚠️ 用户可能撒谎
推荐方案:App Store 年龄分级 + 应用内年龄声明
组合使用:
- App Store 年龄分级设置为 12+(主要防护)
- 应用内注册时要求年龄确认(辅助防护 + 合规记录)
理由:
- App Store 分级阻止儿童下载(系统级保护)
- 应用内确认作为法律合规证据(用户明确声明)
- 双重保护,满足 COPPA 要求
3.2 隐私政策更新
需要添加的内容:
## 儿童隐私保护
本应用不面向 13 岁以下儿童。
**年龄限制**:
- 您必须年满 13 岁才能使用本应用
- 本应用在 App Store 的年龄分级为 12+,系统会阻止 12 岁以下用户下载
- 注册时,您需要确认年满 13 岁
**数据收集**:
- 我们不会故意收集 13 岁以下儿童的个人信息
- 如果我们发现在未经许可的情况下收集了儿童的个人信息,我们将采取措施删除相关信息
**联系我们**:
如果您认为我们可能收集了儿童的个人信息,请联系 privacy@xunmee.com。
四、第八点:数据安全措施实施方案
4.1 加密措施
4.1.1 传输加密(TLS)
当前状态:✅ 已满足
- Supabase 托管服务默认强制 HTTPS
- 所有 API 请求使用 TLS 1.2/1.3 加密
- 无需额外配置
验证方式:
- 使用 SSL Labs 测试:https://www.ssllabs.com/ssltest/
- 目标评级:A 或 A+
4.1.2 静态数据加密(AES-256)
当前状态:✅ 已满足
- Supabase PostgreSQL 默认启用透明数据加密(TDE)
- Supabase Storage 默认启用服务端加密
- 无需额外配置
用户输入是否需要加密字段存储?
回答:不需要
理由:
-
当前数据类型不敏感:
- 用户输入主要是占卜问题、起卦数据
- 不涉及身份证、信用卡、医疗健康等高敏感信息
- 数据库已有 RLS 保护,只有
service_role可访问
-
加密成本:
- 应用层加密会增加查询复杂度(无法索引、无法搜索)
- 增加开发和维护成本
- 性能开销
-
已有保护措施:
- 数据库 TDE(透明数据加密)
- RLS 策略
- JWT 认证
- 传输加密
4.2 访问控制
4.2.1 基于角色的访问控制(RBAC)
当前实现:✅ 已满足
- 数据库 RLS 策略:
anon/authenticated角色被禁止访问业务表 - 后端 Service 层强制校验
owner_id - 只有
service_role可以访问业务数据
RLS 策略示例(来自 20260411_0001_initial_llm_schema.py):
-- 所有业务表启用 RLS
ALTER TABLE table_name ENABLE ROW LEVEL SECURITY;
-- 禁止 anon 和 authenticated 角色访问
CREATE POLICY anon_select_table_name ON table_name
FOR SELECT TO anon USING (false);
CREATE POLICY authenticated_select_table_name ON table_name
FOR SELECT TO authenticated USING (false);
-- 只有 service_role 可以访问(后端使用 service_role key)
4.2.2 Supabase 是否拒绝匿名用户登录?
回答:是的
- Supabase Auth 默认要求认证
- 未登录用户只有
anon角色 - RLS 策略禁止
anon角色访问业务表 - 后端 API 依赖 JWT 验证用户身份
验证流程:
# backend/src/v1/auth/dependencies.py
async def get_current_user(
authorization: str | None = Header(default=None)
) -> AuthUser:
if not authorization:
raise UnauthenticatedError() # 拒绝匿名访问
# 验证 JWT
user = await verify_jwt(authorization)
return user
4.3 审计日志
4.3.1 当前 points_audit_ledger 是否满足要求?
回答:部分满足
points_audit_ledger 现状:
- ✅ 记录积分变更审计
- ✅ 包含
user_id_snapshot、user_email_snapshot - ✅ 包含
event_id、change_type、amount - ✅ 包含时间戳
created_at - ❌ 只记录积分相关操作,不记录其他操作
不满足的审计需求:
- ❌ 用户登录/登出
- ❌ 账户删除
- ❌ 个人信息修改
- ❌ 数据导出请求
建议:扩展审计日志系统
-- 创建通用审计日志表
CREATE TABLE audit_logs (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
user_id uuid REFERENCES auth.users(id) ON DELETE SET NULL,
action varchar(50) NOT NULL,
resource_type varchar(50) NOT NULL,
resource_id uuid,
old_values jsonb,
new_values jsonb,
ip_address inet,
user_agent text,
created_at timestamptz NOT NULL DEFAULT now()
);
CREATE INDEX ix_audit_logs_user_id ON audit_logs(user_id);
CREATE INDEX ix_audit_logs_action ON audit_logs(action);
CREATE INDEX ix_audit_logs_created_at ON audit_logs(created_at);
COMMENT ON TABLE audit_logs IS '通用审计日志:记录用户关键操作';
审计事件类型:
# backend/src/core/audit/events.py
class AuditAction(str, Enum):
# 认证相关
USER_LOGIN = "user.login"
USER_LOGOUT = "user.logout"
USER_DELETE = "user.delete"
# 数据操作
PROFILE_UPDATE = "profile.update"
DATA_EXPORT = "data.export"
# 积分相关(已有 points_audit_ledger)
POINTS_CHANGE = "points.change"
实施优先级:
- P1:创建
audit_logs表 - P2:记录用户登录/登出
- P3:记录账户删除
- P4:记录个人信息修改
4.4 数据泄露响应
4.4.1 响应流程文档
文档位置:docs/security/incident-response.md
响应时间线:
| 阶段 | 时间 | 行动 |
|---|---|---|
| 发现 | T+0 | 确认泄露范围和影响 |
| 评估 | T+1h | 确定严重等级(低/中/高/严重) |
| 控制 | T+4h | 阻止泄露继续,保留证据 |
| 通知 | T+72h | 通知受影响用户(法律要求) |
| 修复 | T+7d | 修复漏洞,加强防护 |
| 复盘 | T+14d | 总结经验,更新流程 |
五、实施优先级与时间线
5.1 优先级矩阵
| 优先级 | 项目 | 预计工时 | 合规必要性 | 状态 |
|---|---|---|---|---|
| P0 | App Store 年龄分级设置 | 0.5 天 | 必须 | 待实施 |
| P0 | 隐私政策更新 | 0.5 天 | 必须 | 待实施 |
| P1 | 应用内年龄确认 | 1-2 天 | 必须 | 待实施 |
| P2 | 通用审计日志表 | 2 天 | 推荐 | 待实施 |
| P3 | 泄露响应流程文档 | 1 天 | 推荐 | 待实施 |
5.2 MVP 上线前必须完成
- ✅ App Store 年龄分级设置为 12+
- ✅ 隐私政策更新(添加儿童隐私章节)
- ✅ 应用内年龄确认(checkbox + 后端记录)
5.3 上线后 3 个月内完成
- 通用审计日志系统
- 泄露响应流程文档
六、验收标准
6.1 COPPA 合规
- App Store 年龄分级设置为 12+(或更高)
- 应用内注册时要求年龄确认
- 后端记录年龄确认时间和方式
- 隐私政策明确说明不面向 13 岁以下用户
6.2 数据安全
- 所有 API 强制 HTTPS(Supabase 默认)
- SSL Labs 评级 A 或以上
- 数据库 RLS 策略正确配置
- 审计日志记录关键操作
- 有书面泄露响应流程
七、关键决策记录
7.1 年龄验证方案
决策:App Store 年龄分级 + 应用内年龄声明
理由:
- Apple 没有直接的年龄验证 API
- App Store 年龄分级(12+)可阻止儿童下载(系统级保护)
- 应用内年龄确认作为法律合规证据
- 零额外成本
实施方式:
- 主要防护:App Store Connect 设置年龄分级为 12+
- 辅助防护:注册时 checkbox 确认年满 13 岁 + 后端记录
7.2 数据加密
决策:依赖 Supabase 默认加密,不额外应用层加密
理由:
- 当前数据类型不敏感(占卜问题、起卦数据)
- Supabase 已提供 TDE + HTTPS
- 应用层加密成本高、收益低
7.3 审计日志
决策:扩展通用审计日志表
理由:
points_audit_ledger只记录积分变更- 需要记录用户登录、删除等操作
- 满足 CCPA/CPRA 审计要求
八、参考资源
8.1 法律法规
8.2 技术参考
下一步行动:
- 在 App Store Connect 设置年龄分级为 12+
- 更新隐私政策文档
- 实现应用内年龄确认(前端 checkbox + 后端记录)