Skip to content

fix(accounts,deploy): oauthBind 核对邮箱、占位 secret 改 0644、推送中继独立限流区 - #199

Merged
Owlbay merged 3 commits into
mainfrom
fix/accounts-deploy-hardening
Nov 2, 2026
Merged

Owlbay merged 3 commits into
mainfrom
fix/accounts-deploy-hardening

Conversation

@Owlbay

@Owlbay Owlbay commented Nov 2, 2026

Copy link
Copy Markdown
Collaborator

变更摘要 / Summary

中文:账号与部署的三处加固(审计 BA-58、BA-59、BA-60)。

  • 根因 1(go/internal/cloud/accounts/oauth.go):oauthBind 在事务内锁账号并重读 fresh,却不比较 fresh.Email 与提供方给的邮箱。事务外读到同邮箱账号后、绑定事务前邮箱被改走(改邮箱确认落在这个窗口)时,会跳过「未验证则清口令、撤设备」的分支,让受害者的第三方身份绑到攻击者仍持有口令的账号上。现在锁住后邮箱不一致答 409 conflict(details.reason = "email_changed"),不绑定、不改账号。
  • 根因 2(deploy/saas/init-secrets.sh):oauth_github_secret、oauth_google_secret、push_relay_apns_key、push_relay_fcm_credentials 是要运维事后写入的占位文件,却走默认 chmod 444,按手册 echo … > secrets/… 写入会 Permission denied。改为 0644(目录仍 0700),手册注明已有部署先 chmod 644。
  • 根因 3(deploy/saas/nginx.conf.template):push.<域> 的 /v1/register 与 app. 站点登录共用 auth 限流区(30r/m),同出口 IP 的推送登记会挤掉登录额度;/v1/push 在代理层不限流。现在 push_register(30r/m,burst 20)和宽松的 push_send(600r/m,burst 100)各用各的区。

English: Three hardening fixes for accounts and deployment (audit BA-58, BA-59, BA-60).

  • Root cause 1 (oauth.go): oauthBind locks the account and re-reads fresh inside the transaction but never compares fresh.Email with the provider's email. If the email is changed between the out-of-transaction read and the binding transaction (an email-change confirmation landing in that window), the "unverified: clear the password and revoke devices" branch is skipped and the victim's third-party identity gets bound to an account whose password the attacker still holds. After the lock, a mismatching email now answers 409 conflict (details.reason = "email_changed") with nothing bound or changed.
  • Root cause 2 (init-secrets.sh): the operator-filled placeholders oauth_github_secret, oauth_google_secret, push_relay_apns_key and push_relay_fcm_credentials got the default chmod 444, so echo … > secrets/… from the runbook failed with Permission denied. They are now 0644 (directory still 0700); the runbook tells existing deployments to chmod 644 first.
  • Root cause 3 (nginx.conf.template): /v1/register on push.<domain> shared the 30r/m auth login zone, so push registrations from one egress IP ate login allowance, and /v1/push had no proxy-level limit. push_register (30r/m, burst 20) and a loose push_send (600r/m, burst 100) now have their own zones.

关联 issue / Related issues

Closes #193

改动范围 / Scope

  • Go 实现(中继与控制面)/ Go implementation(go/internal/cloud/accounts)
  • deploy / 镜像 / images(deploy/saas/)
  • docs(运维手册、CHANGELOG Unreleased)

契约与协议变化 / Contract and protocol changes

无 / None(email_changed 只是 conflict 的 details.reason 新取值,答复形状不变 / email_changed is only a new details.reason value of conflict; the shape is unchanged)

数据库迁移 / Database migrations

无 / None

测试 / Testing

  • 新增 TestOAuthBindRechecksEmail(go/internal/cloud/server/oauth_test.go):测试先锁住账号行,让绑定事务卡在 LockAccount,改掉邮箱后放行;修复前答 200 并建出会话,修复后答 409 且无绑定、口令与设备不动。 / New TestOAuthBindRechecksEmail: the test holds the account row lock so the binding transaction waits in LockAccount, changes the email, then releases; before the fix it answered 200 and created a session, after the fix 409 with nothing bound and password and devices untouched.

  • init-secrets.sh 在临时目录以非 root 实跑:四个占位文件 0644,echo … > 写入成功(master_key 仍 0444)。 / init-secrets.sh was run as non-root in a temp dir: the four placeholders are 0644 and echo … > succeeds (master_key stays 0444).

  • 渲染后的 nginx 配置用 nginx:1.29.3-alpine 的 nginx -t 通过。 / The rendered nginx config passes nginx -t on nginx:1.29.3-alpine.

  • 单元测试 / Unit tests: pnpm go:check(fmt、vet、staticcheck、go test -race、生成物,一次性 PostgreSQL / Redis 容器,已清理 / throwaway PostgreSQL and Redis containers, removed afterwards)

  • 集成测试 / Integration tests: 无 / None

  • 跨仓端到端 / Cross-repo e2e: 无 / None

  • 镜像 / Images: 无 / None

  • pnpm check 与 / and pnpm repo:test

检查清单 / Checklist

需要维护者提供的外部条件 / External prerequisites

已有部署:占位文件已是 0444 的,写入前先 chmod 644;代理配置改动需 docker compose --profile proxy up -d --force-recreate proxy 重新加载。 / Existing deployments: placeholders already 0444 need chmod 644 before writing; reload the proxy with docker compose --profile proxy up -d --force-recreate proxy.

已知限制 / Known limitations

init-secrets.sh 不覆盖已有文件,所以不会自动改已有部署里的权限。 / init-secrets.sh never overwrites existing files, so it does not change permissions on existing deployments.

@Owlbay
Owlbay force-pushed the fix/accounts-deploy-hardening branch from 70bd2f0 to deb3896 Compare November 2, 2026 21:29
@Owlbay
Owlbay merged commit 3b213c4 into main Nov 2, 2026
11 of 12 checks passed
@Owlbay
Owlbay deleted the fix/accounts-deploy-hardening branch November 2, 2026 21:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant