Files
eryao/docs/discussions/coppa-security-implementation-plan.md

513 lines
14 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# COPPA 儿童保护与数据安全措施实施方案
**文档状态**: 草案
**创建日期**: 2026-04-17
**关联文档**: `docs/discussions/legal-compliance-us.md` 第七点、第八点
---
## 一、背景与法律要求
### 1.1 COPPA(儿童在线隐私保护法)要求
针对 13 岁以下用户,COPPA 要求:
1. **年龄验证**:必须采取合理措施验证用户年龄
2. **禁止收集**:不得收集 13 岁以下儿童个人信息
3. **隐私政策披露**:隐私政策必须明确说明不面向 13 岁以下用户
### 1.2 CCPA/CPRA 数据安全要求
针对数据安全措施,隐私政策声明的安全措施必须真实实施:
1. **加密措施**AES-256 数据加密、TLS 1.3 传输加密
2. **访问控制**:基于角色的访问控制(RBAC
3. **监控审计**:审计日志
4. **泄露响应**72 小时内通知机制
---
## 二、当前系统现状分析
### 2.1 用户认证流程
**现有实现**
- 认证方式:邮箱 + OTP(验证码)登录
- 认证提供方:Supabase Auth
- 用户表:`auth.users`Supabase 内置)
- 扩展表:`public.profiles`
**缺失项**
- ❌ 无年龄验证机制
### 2.2 数据安全现状
**已有措施**
- ✅ Supabase Auth 内置 JWT 认证(HS256 签名)
- ✅ 数据库 RLSRow 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 会自动阻止不符合年龄要求的用户下载。
**实施步骤**
1. **登录 App Store Connect**
2. **选择应用 → "App 信息" → "年龄分级"**
3. **填写年龄分级问卷**
4. **设置年龄分级为 12+(或更高)**
**年龄分级说明**
- **4+**:无 objectionable 内容
- **9+**:可能包含轻微的卡通暴力/幻想暴力
- **12+**:可能包含轻微的现实暴力/偶尔粗俗幽默/轻微模拟赌博
- **17+**:可能包含频繁/强烈的现实暴力/成人内容
**设置 12+ 的效果**
- App Store 会阻止 12 岁以下用户下载应用
- 家长控制功能会根据年龄分级限制儿童访问
**优点**
- ✅ 零成本,无需开发
- ✅ Apple 系统级保护
- ✅ 自动生效
**缺点**
- ⚠️ 仅防止下载,不能防止借用他人设备/账号
- ⚠️ 用户可能谎报年龄注册 Apple ID
#### 方案二:应用内年龄声明(推荐 - 辅助防护)
**原理**:在注册流程中添加年龄确认 checkbox 或弹窗。
**实施步骤**
1. **前端修改**
```dart
// 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,
),
),
],
),
```
2. **后端验证**
```python
# 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 岁"
)
```
```python
# 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)
```
3. **数据库记录**
```sql
-- 在 profiles 表添加年龄确认字段
ALTER TABLE profiles
ADD COLUMN age_confirmed_at timestamptz,
ADD COLUMN age_confirmation_method varchar(20) DEFAULT 'self_declaration';
```
4. **本地化文本**
```json
// apps/lib/l10n/app_zh.arb
{
"ageConfirmationText": "我确认已年满 13 岁,或已获得家长/监护人同意使用本应用",
"@ageConfirmationText": {
"description": "年龄确认声明文本"
}
}
```
```json
// 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 年龄分级 + 应用内年龄声明
**组合使用**
1. **App Store 年龄分级设置为 12+**(主要防护)
2. **应用内注册时要求年龄确认**(辅助防护 + 合规记录)
**理由**
- App Store 分级阻止儿童下载(系统级保护)
- 应用内确认作为法律合规证据(用户明确声明)
- 双重保护,满足 COPPA 要求
### 3.2 隐私政策更新
**需要添加的内容**
```markdown
## 儿童隐私保护
本应用不面向 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 默认启用服务端加密
- 无需额外配置
**用户输入是否需要加密字段存储?**
**回答:不需要**
理由:
1. **当前数据类型不敏感**
- 用户输入主要是占卜问题、起卦数据
- 不涉及身份证、信用卡、医疗健康等高敏感信息
- 数据库已有 RLS 保护,只有 `service_role` 可访问
2. **加密成本**
- 应用层加密会增加查询复杂度(无法索引、无法搜索)
- 增加开发和维护成本
- 性能开销
3. **已有保护措施**
- 数据库 TDE(透明数据加密)
- RLS 策略
- JWT 认证
- 传输加密
### 4.2 访问控制
#### 4.2.1 基于角色的访问控制(RBAC)
**当前实现**:✅ 已满足
- 数据库 RLS 策略:`anon`/`authenticated` 角色被禁止访问业务表
- 后端 Service 层强制校验 `owner_id`
- 只有 `service_role` 可以访问业务数据
**RLS 策略示例**(来自 `20260411_0001_initial_llm_schema.py`):
```sql
-- 所有业务表启用 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 验证用户身份
**验证流程**
```python
# 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`
- ❌ 只记录积分相关操作,不记录其他操作
**不满足的审计需求**
- ❌ 用户登录/登出
- ❌ 账户删除
- ❌ 个人信息修改
- ❌ 数据导出请求
**建议:扩展审计日志系统**
```sql
-- 创建通用审计日志表
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 '通用审计日志:记录用户关键操作';
```
**审计事件类型**
```python
# 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 上线前必须完成
1. ✅ App Store 年龄分级设置为 12+
2. ✅ 隐私政策更新(添加儿童隐私章节)
3. ✅ 应用内年龄确认(checkbox + 后端记录)
### 5.3 上线后 3 个月内完成
1. 通用审计日志系统
2. 泄露响应流程文档
---
## 六、验收标准
### 6.1 COPPA 合规
- [ ] App Store 年龄分级设置为 12+(或更高)
- [ ] 应用内注册时要求年龄确认
- [ ] 后端记录年龄确认时间和方式
- [ ] 隐私政策明确说明不面向 13 岁以下用户
### 6.2 数据安全
- [ ] 所有 API 强制 HTTPSSupabase 默认)
- [ ] SSL Labs 评级 A 或以上
- [ ] 数据库 RLS 策略正确配置
- [ ] 审计日志记录关键操作
- [ ] 有书面泄露响应流程
---
## 七、关键决策记录
### 7.1 年龄验证方案
**决策**App Store 年龄分级 + 应用内年龄声明
**理由**
1. Apple 没有直接的年龄验证 API
2. App Store 年龄分级(12+)可阻止儿童下载(系统级保护)
3. 应用内年龄确认作为法律合规证据
4. 零额外成本
**实施方式**
- **主要防护**App Store Connect 设置年龄分级为 12+
- **辅助防护**:注册时 checkbox 确认年满 13 岁 + 后端记录
### 7.2 数据加密
**决策**:依赖 Supabase 默认加密,不额外应用层加密
**理由**
1. 当前数据类型不敏感(占卜问题、起卦数据)
2. Supabase 已提供 TDE + HTTPS
3. 应用层加密成本高、收益低
### 7.3 审计日志
**决策**:扩展通用审计日志表
**理由**
1. `points_audit_ledger` 只记录积分变更
2. 需要记录用户登录、删除等操作
3. 满足 CCPA/CPRA 审计要求
---
## 八、参考资源
### 8.1 法律法规
- [COPPA 官方指南](https://www.ftc.gov/business-guidance/privacy-security/childrens-privacy)
- [CCPA 官方文本](https://oag.ca.gov/privacy/ccpa)
### 8.2 技术参考
- [App Store 年龄分级](https://developer.apple.com/help/app-store-connect/reference/age-ratings/)
- [Supabase Auth 文档](https://supabase.com/docs/guides/auth)
- [Supabase RLS 文档](https://supabase.com/docs/guides/auth/row-level-security)
---
**下一步行动**
1. 在 App Store Connect 设置年龄分级为 12+
2. 更新隐私政策文档
3. 实现应用内年龄确认(前端 checkbox + 后端记录)