请求验证顺序
| 层次 | 检查项 | 结果 |
|---|---|---|
| Android应用访问密钥 | X-Android-Key或tingwen_app Cookie与运行态secret恒时比较 |
不匹配403,拒绝进入业务 |
| Host | 仅127.0.0.1或localhost | 其他Host拒绝 |
| 来源 | POST和/api/session在提供Origin时检查其等于当前scheme/host/port;Sec-Fetch-Site不得为cross-site |
不同源拒绝;缺Origin本身不拒绝,无放开CORS |
| 本机会话 | 所有POST要求X-Local-Token等于本进程TOKEN |
缺失/过期403,带明确code |
| 内容类型 | POST必须JSON | 非JSON为415 |
| 请求体 | 最大64×1024×1024字节 | 超出413 |
| 业务校验 | 标题、音色、图片、状态、版本、额度等 | 不通过时400/409等 |
TOKEN通过secrets.token_urlsafe(32)生成,仅在运行内存;Android secret同样不写任务或草稿。首页Cookie设置HttpOnly与SameSite=Strict;回环HTTP没有设置Secure。首页和会话响应为no-store。HttpOnly能限制页面JS读取Cookie,不能替代主WebView导航控制和设备安全边界。
前端仅对服务器明确给出的local_session_expired拒绝进行一次会话恢复与重放:获取同源/api/session、校验产品名与token后重新发原请求。该拒绝发生在任务/模型处理之前,因此可以安全恢复。普通网络断连、Seedream超时或“响应未知”不属于可自动重发场景,防止产生第二次付费请求。
主WebView设置与导航
- 开启JavaScript和DOM storage,关闭file access;content access供系统选择器交互使用。
- 拒绝第三方Cookie、禁止mixed content、媒体播放要求用户手势;WebView摄像头/麦克风权限请求直接deny。
- 主页面仅允许当前
http://127.0.0.1:<port>同源导航。主机需字面127.0.0.1、无userInfo、合法且相同端口。 - 当前源资源、当前源拥有的blob、data和about:blank按代码规则放行;其他资源请求返回403。外部http/https/mailto/tel交给系统应用。
- 图片放大使用无AndroidNative桥的独立预览WebView,需用户手势;关闭预览会销毁其资源,避免把原生文件能力带给任意外部页面。
网络安全XML默认拒绝cleartext,仅回环域名放行。Python照片下载自行允许并验证公共HTTP/HTTPS,不能把XML设置误解为Python的网上图片只支持HTTPS。APK并未实现证书钉扎、设备加密数据仓库或跨设备身份服务。
依据:app.py:369–419、MainActivity.java:146–239、ExportPolicy.java:9–27、res/xml/network_security_config.xml。