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

14 KiB
Raw Blame History

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.usersSupabase 内置)
  • 扩展表:public.profiles

缺失项

  • 无年龄验证机制

2.2 数据安全现状

已有措施

  • Supabase Auth 内置 JWT 认证(HS256 签名)
  • 数据库 RLSRow Level Security)策略 - anon/authenticated 角色被禁止访问业务表
  • 日志敏感字段过滤(core/config/settings.pylog_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. 前端修改
// 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,
      ),
    ),
  ],
),
  1. 后端验证
# 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)
  1. 数据库记录
-- 在 profiles 表添加年龄确认字段
ALTER TABLE profiles 
ADD COLUMN age_confirmed_at timestamptz,
ADD COLUMN age_confirmation_method varchar(20) DEFAULT 'self_declaration';
  1. 本地化文本
// 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 年龄分级 + 应用内年龄声明

组合使用

  1. App Store 年龄分级设置为 12+(主要防护)
  2. 应用内注册时要求年龄确认(辅助防护 + 合规记录)

理由

  • 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 加密
  • 无需额外配置

验证方式

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):

-- 所有业务表启用 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_snapshotuser_email_snapshot
  • 包含 event_idchange_typeamount
  • 包含时间戳 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 上线前必须完成

  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 法律法规

8.2 技术参考


下一步行动

  1. 在 App Store Connect 设置年龄分级为 12+
  2. 更新隐私政策文档
  3. 实现应用内年龄确认(前端 checkbox + 后端记录)