# 给 Helix 服务账号开 `gmail.readonly`(域级委派)

> **2026-08-26 实测结论,先看这一段。**
>
> `gmail.readonly` **已经授权成功了** —— 用真实用户(`it-notifications@`、
> `shuai.feng@`)去冒充,读信是通的。
>
> 但 **`security@pingcap.com` 是一个邮件组,不是邮箱**。域级委派只能冒充**真实
> 用户**,所以指向 `security@` 的轮询**永远**会返回 `unauthorized_client` ——
> 和授权了哪些 scope 无关。实测:连 Helix 每天都在用的 `gmail.send`,冒充
> `security@` 也一样失败,冒充真实用户就正常。
>
> **要把「白帽子来信轮询」真正打开,还差一步(需要你操作)**:
> 建一个**真实用户账号** `bounty-intake@pingcap.com`(不是别名、不是组),
> 把 `security@` 组的来信投递/转发一份进去,然后启用那个定时任务。代码里的
> 白名单已经写好这个地址了,不用改代码。
>
> 历史那 149 份报告的正文**不受影响**:它们的 Gmail 线程 ID 是在平台负责人自己
> 的邮箱里的(他是 security@ 组成员),那个邮箱可以冒充,所以正文导入照常能跑。


**为什么需要**:赏金台的「白帽子来信轮询」(`bounty.mail_intake_poll`)要读
`security@pingcap.com` 的收件箱,把新来信排进「待确认收件」队列。这个服务账号
今天只有 `gmail.send`(发信),没有读的权限,所以那个定时任务**出厂就是停用的** ——
没授权就开,它会每 15 分钟失败一次,人很快就会学会无视失败通知。

代码改不了这件事,这是 Google 管理后台的动作。

---

## 你要操作的地址

<https://admin.google.com/ac/owl/domainwidedelegation>

(管理控制台 → 安全 → 访问权限和数据控制 → API 控制 → 域名全域委派)

需要 Workspace 超级管理员权限。

## 这个服务账号是谁

| | |
|---|---|
| **客户端 ID**(就填这个) | `114264048969472896919` |
| 服务账号邮箱 | `helix-provisioning@gam-project-hsgkt.iam.gserviceaccount.com` |
| GCP 项目 | `gam-project-hsgkt` |

## 要加的这一条

```
https://www.googleapis.com/auth/gmail.readonly
```

---

## 步骤

1. 打开上面的地址,在列表里找到客户端 ID `114264048969472896919` 那一行。
2. 点这一行右边的 **「修改」/ Edit**。
3. 会弹出一个框,里面是**已经授权的所有 scope,用英文逗号连着的一长串**。

   > ⚠️ **这一步是唯一会出事的地方。**
   >
   > 这个框是**整体覆盖**的 —— 你保存什么,它就变成什么。如果你把里面清空、
   > 只粘 `gmail.readonly`,那么 Helix 的**建号、改号、邮件组、离职数据转移、
   > 发信**会在下一次执行时**全部失败**,而且不会有任何提示,只会看到一堆
   > 403。
   >
   > **不要重打这一串,也不要替换。** 把光标放到**最末尾**,先打一个英文逗号
   > `,`,再粘上面那一条。
   >
   > 保险起见:改之前先把框里原来的内容整段复制出来存一下,万一存错了能贴回去。

4. 追加之后,末尾应该长这样(前面 `...` 是原有的那些,保持不动):

   ```
   ...,https://www.googleapis.com/auth/gmail.send,https://www.googleapis.com/auth/gmail.readonly
   ```

5. 点 **「授权」/ Authorize** 保存。
6. 等 **5–10 分钟**。Google 这边的授权是最终一致的,刚存完立刻试大概率还是 403,
   这不代表你操作错了。

## 怎么确认成功了

> ⚠️ 下面这段验证要等 `bounty-intake@pingcap.com` 建好之后才做得通 —— 在那之前
> 任务指向的邮箱不存在,一定会失败。

进 Helix → **管理与配置 → 定时任务**,找到 **`bounty.mail_intake_poll`
(白帽子来信轮询)**:

1. 先点 **「立即执行一次」**,不用先启用 —— 手动跑一次是安全的,它只读邮件、
   只往「待确认收件」队列里排,**永远不会自动建报告**。
2. 看执行结果:
   - **成功**,返回类似 `listed / queued / already_seen` 的计数 → 授权好了,
     可以把这个任务**启用**。
   - 报 `BOUNTY_MAIL_SCOPE_MISSING`(HTTP 502)→ 还没生效或者没加上。再等
     5 分钟;超过 15 分钟还这样,回第 3 步看那一串里到底有没有
     `gmail.readonly`。
   - 报 `BOUNTY_MAIL_MAILBOX_NOT_ALLOWED` → 任务配置里的 mailbox 被人改过。
     可读的邮箱是代码里的白名单(`security@pingcap.com` /
     `bounty-intake@pingcap.com`),不是配置里的自由文本。

## 顺带说明

- 这个 scope 是**只读**的,拿不到删信、改信、发信的能力(发信是另一条
  `gmail.send`,本来就有)。
- 可读的邮箱在代码里写死成白名单,不是谁改一下任务配置就能去读别人的邮箱 ——
  域级委派本身可以冒充**任意** Workspace 用户,所以这层白名单是故意加的。
- 邮件正文**不进数据库**,队列里只存主题、发件人、时间和一段摘要。

## 不想给整个 `security@` 的读权限?

也可以:在 Workspace 里建一个 `bounty-intake@pingcap.com`,把 `security@` 里
白帽子那一类来信转发过去,然后把定时任务的 `mailbox` 配成它。这个地址已经在
代码白名单里了,不用改代码。
