---
id: 003
title: epsilon 生产 PHONE_SALT 未设，跑默认 dev-salt-not-for-prod
status: closed
labels: [security, prod, epsilon]
assignee: team-lead
closed_at: 2026-04-23
created_at: 2026-04-23
---

# #003 epsilon 生产 PHONE_SALT 未设，跑默认 dev-salt

## 发现

alpha provision 时顺手查了 epsilon 的 live env：

```
$ systemctl show xiangqin -p Environment
Environment=PATH=... XIANGQIN_SMS_PROVIDER=aliyun XIANGQIN_PAYMENT_PROVIDER=alipay \
  XIANGQIN_BASE_URL=https://xq.agentaily.com VAULT_DATA_DIR=/opt/xiangqin/vault-data
```

**没有 `XIANGQIN_PHONE_SALT`**。代码 fallback 到 `src/xiangqin/db.py`:

```python
PHONE_SALT = os.environ.get("XIANGQIN_PHONE_SALT", "dev-salt-not-for-prod").encode()
```

所以 epsilon 当前所有已注册用户的 `phone_hash` 用的是公开可见的 `dev-salt-not-for-prod`。

## 影响

- 手机号 hash 失去加盐防撞库的意义（salt 是公开字符串，攻击者用相同 salt 算彩虹表可反查）
- M4 alpha 还没发 live，**现在改还来得及**（老用户 = 老板本人测试账号，可接受手动重注册）
- 发 live 以后再改会让所有历史 phone_hash 失配 → 用户被迫重注册

## 方案

### 1. 生成合规 salt

```bash
openssl rand -hex 32
```

存到 vault 里（新 resource `xiangqin-epsilon-phone-salt`）。

### 2. 写入 epsilon systemd

有两条路：

**A. 改 `systemd/xiangqin.service`** 文件加 `Environment=XIANGQIN_PHONE_SALT=...`
- 好处：版本化可见
- 坏处：salt 进 git 仓 → 不行，salt 就是密码

**B. 用 drop-in override（推荐，和 alpha 做法一致）**
```
/etc/systemd/system/xiangqin.service.d/override.conf:
[Service]
Environment=XIANGQIN_PHONE_SALT=<32 hex chars>
```
- 好处：不进 git；每台机器独立 salt
- 坏处：人肉 ssh 配一次（未来靠 vault install 铺到生产机）

### 3. 清库（必须）

改 salt 后老 phone_hash 全失配。epsilon 库清：

```sql
DELETE FROM users;    -- M4 前测试用户都是老板自己，可清
DELETE FROM register_requests;
```

或干脆 `rm /opt/xiangqin/data/xiangqin.db` 重建（db.py 里有 `_ensure_schema` 会自动建表）。

### 4. 同时把 `XIANGQIN_PHONE_SALT` 加到 `systemd/xiangqin.service` 的注释/placeholder，提醒未来 provision 新机器要单独配

## 时机

**M4 发 live 之前**必须做完。live 之后改会伤用户。

## 相关

- 来源：issue #001 alpha provision 时副带发现（2026-04-23）
- alpha 已经在 provision 里自动生成独立 salt（`openssl rand -hex 32`），只是 epsilon 历史遗漏
