status: closed(用户已决策接受,变更关闭) created: 2026-09-07 closed: 2026-09-07(用户决策:内网私仓 + 沙箱密钥可接受,后期生产密钥不提交) complexity: 🟡中等 规则依据:
AGENTS.md5.9 安全红线「禁止硬编码密钥、AK/SK」、5.12 数据库脚本维护规范(无关)、2.4 环境变量 关联变更:code-copilot/changes/hotel-pay-mock-hardening/(资金逻辑收口,性质不同,不可合并)
本变更已关闭。
| 项 | 状态 |
|---|---|
| 方案编写 | ✅ 本文件 |
| 代码/配置改动 | ❌ 一行未动 |
| 密钥轮换 | ❌ 未执行 |
| 用户决策 | ✅ 已决策接受:git 是内网私有仓库(192.168.8.4),不在外网;密钥是沙箱密钥;后期如有必要,会直接不提交生产的密钥 |
关闭原因:用户评估风险可接受,本变更不再推进。后续生产密钥不入库由用户把控。
更正记录:
application.yml L42-43 spring.profiles.active: dev,当前启动会加载 application-dev.yml,hotel.alipay.* 全部有值,不会崩。application-dev.yml 中的支付宝沙箱应用私钥(RSA2,PKCS#8 全文约 1600 字符)与支付宝公钥以明文形式写死,且该文件被 git 跟踪并已推送到远端。命中 AGENTS.md 5.9「禁止硬编码密钥、AK/SK、数据库密码」。
| 维度 | 结论 |
|---|---|
| 直接资金损失 | 🟢 无 —— 这是沙箱密钥(gateway-url 为 openapi-sandbox.dl.alipaydev.com,sandbox: true),沙箱不涉及真实资金 |
| 合规红线 | 🔴 硬违规 —— AGENTS.md 5.9 不区分沙箱与生产,「禁止硬编码密钥」是无条件红线 |
| 实际危害 | 🟡 中 —— ① 他人可用你的沙箱 appid + 私钥以你的身份发起交易、污染测试数据;② 沙箱密钥对存在被复用到生产的现实可能(同一开发者图省事),一旦复用即升级为高危;③ 私钥泄露后无法「改密码」,只能重新生成密钥对并更换应用公钥 |
| 扩散面 | 🔴 已进远端历史 —— commit 65508d9「支付宝沙箱支付功能提交」(2026-09-01)已存在于 origin/master,git log -S 可检索到私钥全文 |
.gitignore 里已存在但失效的 **/application-dev.yml 规则真正起作用AGENTS.md 的意图表述(它是对的,见 2.4)| 文件 | 是否被 git 跟踪 | 是否含明文密钥 |
|---|---|---|
forge-server/forge-admin-server/src/main/resources/application-dev.yml |
🔴 是(git ls-files 命中) |
🔴 是,L106-118 hotel.alipay 块:app-id / private-key(L110)/ alipay-public-key(L112)/ gateway-url / notify-url / sandbox |
forge-server/forge-app-server/src/main/resources/application-dev.yml |
🔴 是 | 🔴 是,L85-99 同一份密钥(两份文件密钥完全相同),另多一个 return-url(L97) |
forge-server/*/src/main/resources/application-dev.example.yml |
✅ 是(应当提交) | 🟢 否 —— Select-String -Pattern "alipay" 零命中,模板文件里根本没有 hotel.alipay 块 |
forge-server/*/src/main/resources/application.yml(生产) |
✅ 是 | 🟢 否 —— 零命中,生产 yml 的 hotel: 块只有 qr 和 pay(admin-server L222-239) |
其它顺带发现:application-dev.yml L19-20 的数据库口令为 root / root(本地开发默认值,敏感度低,但同属「明文口令入库」)。
.gitignore 规则已存在但失效 —— 修正我此前的建议我在提出本变更时的原话是「把 application-dev.yml 加入 .gitignore」。这个说法是错的,核实结果:
$ git check-ignore -v --no-index forge-server/forge-admin-server/src/main/resources/application-dev.yml
.gitignore:83:**/application-dev.yml forge-server/.../application-dev.yml
$ git check-ignore -v forge-server/forge-admin-server/src/main/resources/application-dev.yml
(无输出,退出码 1)
.gitignore 第 83 行早就有 **/application-dev.yml,规则本身完全正确、能匹配--no-index 时返回空 —— 因为 .gitignore 只对未跟踪文件生效,该文件已在索引中,规则被完全绕过.gitignore 规则,需要的是 git rm --cached 把它从索引中移除,让既有规则接管同时修正另一处说法:我此前说「AGENTS.md 2.4 把它归为『本地配置(不可提交)』,与实际不符,也该修」。AGENTS.md 的表述是对的(2.4 明确把 application-dev.yml 列为「后端本地配置」、把 application-dev.example.yml 列为「配置模板(可提交)」),不符的是仓库实际状态。要修的是状态,不是文案。
⚠️
AGENTS.md2.4 唯一确实该修的是路径前缀:写的是forge/forge-admin-server/...,真实路径是forge-server/forge-admin-server/...。这与 2.2 的cd forge && mvn clean install(仓库根forge/是空目录,真实 Maven 根为forge-server/)同源,属同一处文档偏差,建议一并修。
原判断:AlipayConfig 5 个 @Value 无默认值 + 生产 application.yml 无 hotel.alipay 块 → 生产启动崩溃(P0)。
更正:application.yml L42-43 spring.profiles.active: dev,且 docker-compose.yml / .env.example 无任何 profile 覆盖,项目也没有 application-prod.yml。因此当前启动(包括 docker 部署)都会加载 application-dev.yml,hotel.alipay.* 全部有值,不会崩。
我错在哪:把 application.yml 当成「生产专用配置」,忽略了它 L43 默认激活 dev profile,application-dev.yml 会被合并加载。
origin → http://192.168.8.4:3000/user5/forge_admin.git(内网 Gitea,非公网仓库)65508d9 已在 origin/master(git branch -r --contains 命中)顺序原则:先止血(轮换),再断源(占位化),最后清历史。 只要旧私钥仍然有效,任何仓库层面的清理都是装饰性的 —— 攻击者手里的密钥照样能用。
这一步做完,泄露的私钥立即作废,后续步骤的风险等级随之下降。
在支付宝开放平台操作:
9021000166699753)验证轮换生效:用旧私钥调一次沙箱接口,应返回验签失败(isv.invalid-signature)。
📌 待你确认(第 6 节 Q1):是否有其它环境/同事正在使用这对沙箱密钥?轮换会立即打断他们的调试。
改动面:6 份 yml(与 hotel-pay-mock-hardening 共享同一批文件,见第 5 节风险 ③)
application.yml(admin-server L239 之后 / app-server L121 之后,新增整个 alipay 块)hotel:
qr:
base-url: ${FORGE_HOTEL_QR_BASE_URL:}
path: ${FORGE_HOTEL_QR_PATH:/r/}
pay:
mock-enabled: ${FORGE_HOTEL_PAY_MOCK_ENABLED:false}
# 支付宝支付配置。全部通过环境变量注入,禁止在仓库中写明文密钥(AGENTS.md 5.9)。
# 五个必填项无代码级默认值:未注入时 AlipayConfig 占位符解析失败、服务启动即崩,
# 这是刻意的 fail-fast —— 生产缺支付配置就不该起来,避免起来后静默走到 Mock 分支。
alipay:
app-id: ${FORGE_HOTEL_ALIPAY_APP_ID:}
private-key: ${FORGE_HOTEL_ALIPAY_PRIVATE_KEY:}
alipay-public-key: ${FORGE_HOTEL_ALIPAY_PUBLIC_KEY:}
gateway-url: ${FORGE_HOTEL_ALIPAY_GATEWAY_URL:https://openapi.alipay.com/gateway.do}
notify-url: ${FORGE_HOTEL_ALIPAY_NOTIFY_URL:}
return-url: ${FORGE_HOTEL_ALIPAY_RETURN_URL:}
sandbox: ${FORGE_HOTEL_ALIPAY_SANDBOX:false}
⚠️ 这里有个设计选择需你定(第 6 节 Q2):
- 方案 A(上面写的):给空串默认值
:}→ 生产不配也能启动,但支付功能不可用(调支付宝时报错)- 方案 B:不给默认值 → 生产不配就启动即崩(fail-fast)
我推荐 方案 A + 空串,理由:
AlipayConfig是@Configuration,方案 B 会让根本不用酒店支付功能的部署也起不来(forge-hotel是forge-business的一部分,被两个服务无条件扫描)。空串默认值 + 调用时报错,故障面更可控。但方案 A 需要配套改动:
AlipayConfig.alipayClient()(L50-54)应在appId/privateKey为空时不创建 Bean 或创建后在调用处显式报「支付未配置」,否则DefaultAlipayClient会用空密钥初始化,错误信息晦涩。这一改动超出纯配置范围,需你同意才做(第 6 节 Q2)。
application-dev.yml(两份,把明文密钥换成占位 + 本地兜底)hotel:
qr:
base-url: http://192.168.10.4:3001
path: "/#/pages/hotel/scan-bind"
pay:
mock-enabled: true
# 支付宝沙箱配置。密钥不写死在文件里(本文件虽已 ignore,但历史上曾被提交,见变更方案 Step 1)。
# 三种注入方式任选其一:
# ① IDEA Run Configuration → Environment variables(推荐,重启即生效)
# ② 系统环境变量
# ③ 本机 ~/.forge/secrets/alipay.properties(若采用 Step 2-D 的 spring.config.import 方案)
alipay:
app-id: ${FORGE_HOTEL_ALIPAY_APP_ID:9021000166699753}
private-key: ${FORGE_HOTEL_ALIPAY_PRIVATE_KEY:}
alipay-public-key: ${FORGE_HOTEL_ALIPAY_PUBLIC_KEY:}
gateway-url: ${FORGE_HOTEL_ALIPAY_GATEWAY_URL:https://openapi-sandbox.dl.alipaydev.com/gateway.do}
notify-url: ${FORGE_HOTEL_ALIPAY_NOTIFY_URL:http://pc948923.natappfree.cc/hotel/open/pay/notify}
return-url: ${FORGE_HOTEL_ALIPAY_RETURN_URL:http://192.168.10.4:3001/#/pages/hotel/customer/pay} # 仅 app-server
sandbox: true
app-id / gateway-url / notify-url / return-url / sandbox 保留明文默认值 —— 它们不是密钥,写死可让你少配 5 个环境变量private-key 和 alipay-public-key 默认值为空 —— 这两个是必须外提的notify-url 的 natapp 免费隧道域名(pc948923.natappfree.cc)与你账号绑定,敏感度中等。是否也外提见第 6 节 Q3application-dev.example.yml(两份,新增带说明的空模板)当前模板文件完全没有 hotel.alipay 块(Select-String 零命中),新人 cp 之后会撞上 2.3 的启动崩溃。需补齐:
hotel:
# ... qr / pay 块保持与 dev 一致 ...
# 支付宝沙箱配置。以下两项必须自行填入,仓库中不提供任何真实值。
# 获取方式:open.alipay.com → 开发者中心 → 沙箱应用 → 接口加签方式 → 用官方密钥工具生成 RSA2 密钥对
alipay:
app-id: 你的沙箱APPID
private-key: 你的应用私钥(RSA2,PKCS#8 单行,不要带 -----BEGIN----- 头尾)
alipay-public-key: 上传应用公钥后平台返回的支付宝公钥
gateway-url: https://openapi-sandbox.dl.alipaydev.com/gateway.do
notify-url: 你的内网穿透地址/hotel/open/pay/notify
return-url: 你的H5地址/#/pages/hotel/customer/pay
sandbox: true
若不想每次配环境变量,可用 Spring Boot 的 spring.config.import 引入本机文件:
# application-dev.yml 顶部
spring:
config:
import: "optional:file:${user.home}/.forge/secrets/alipay.properties"
# ~/.forge/secrets/alipay.properties(在仓库外,天然不会被提交)
FORGE_HOTEL_ALIPAY_PRIVATE_KEY=xxx
FORGE_HOTEL_ALIPAY_PUBLIC_KEY=xxx
optional: 前缀保证文件不存在时不报错~/.forge/secrets/crypto.properties 约定(见 application-dev.yml L67 注释)同源,风格一致spring.config.import 引入的 properties 能否被 ${} 占位符解析(Spring Boot 3.2 支持,但需验证加载顺序)—— 见第 6 节 Q4.gitignore 规则生效# 从索引移除,但保留本地文件(--cached 是关键,不加会删掉你的本地配置)
git rm --cached forge-server/forge-admin-server/src/main/resources/application-dev.yml
git rm --cached forge-server/forge-app-server/src/main/resources/application-dev.yml
# 验证:应输出 .gitignore:83:**/application-dev.yml
git check-ignore -v forge-server/forge-admin-server/src/main/resources/application-dev.yml
git rm --cached 提交后,该 commit 记录的是文件删除。其他开发者 git pull 时,git 会删掉他们本地的 application-dev.yml(因为在他们工作区里该文件是跟踪状态)。
必须先做的事:
application-dev.yml📌 待你确认(第 6 节 Q5):当前仓库有几个活跃协作者?是否可以协调一个统一的时间窗口?
Step 3 必须在 Step 2 之后:先占位化再脱钩。若先 git rm --cached,那么在你换密钥的这段时间里,明文密钥文件仍在你本地且已脱离版本控制 —— 看似安全,实则远端历史里的旧密钥仍然有效,止血效果为零。
只有在 Step 1 轮换完成后,这一步才是"锦上添花";如果没轮换,这一步是自欺欺人。
| 方案 | 命令 | 破坏性 |
|---|---|---|
git filter-repo(官方推荐) |
git filter-repo --replace-text expressions.txt |
🔴 重写全部 commit hash |
| BFG Repo-Cleaner | bfg --replace-text passwords.txt |
🔴 同上 |
后果(必须全部接受才能做):
git push --force → AGENTS.md 与我的操作准则都要求:force push 到 master 必须你明确授权,我不会自行执行我的建议:
| 影响 | 说明 | 规避 |
|---|---|---|
🔴 改完 application-dev.yml 后启动失败 |
private-key 占位符无值 → AlipayConfig 拿到空串 → DefaultAlipayClient 初始化不报错,但调支付宝接口时验签失败 |
执行 Step 2 前,先在 IDEA Run Configuration 里配好 FORGE_HOTEL_ALIPAY_PRIVATE_KEY / FORGE_HOTEL_ALIPAY_PUBLIC_KEY |
| 🟡 支付宝沙箱支付调试中断 | 轮换密钥后,新密钥未配好之前无法调通 | 轮换与配置在同一时间窗口内完成;期间可用 hotel.pay.mock-enabled=true 走 Mock 分支调试业务流转(dev 已开启) |
| 🟢 H5 前端 | 零影响 —— 本变更不动任何前端文件 | — |
| 🟢 二维码功能 | 零影响 —— hotel.qr.* 块不动 |
— |
| 🟢 Mock 支付开关 | 零影响 —— hotel.pay.mock-enabled 不动 |
— |
⚠️ 执行时机建议:选一个你不需要立即调试支付宝真实支付的时间点。若近期要演示/验收支付流程,先跳过 Step 1-2,只做记录。
| # | 风险 | 缓解 |
|---|---|---|
| ① | 🔴 轮换打断协作者:沙箱 appid 是共享的,换密钥后其他人本地立刻失效 | Step 1 前先确认使用范围(Q1);轮换后同步新密钥给协作者(通过安全渠道,不走 git) |
| ② | 🔴 Step 3 删除协作者本地配置:git rm --cached 提交后,别人 pull 会丢文件 |
提前通知备份,提交信息写明 |
| ③ | 🟡 6 份 yml 与 hotel-pay-mock-hardening 共享同一 hotel: 块 |
两变更不可并行改同一文件;本变更应在 hotel-pay-mock-hardening 的 HARD-GATE 签署之后再动 yml,避免回滚互相污染 |
| ④ | 🟡 Step 2-A 的方案 B(无默认值)会让不用酒店支付的部署起不来 | 采方案 A(空串默认值)+ 配套改 alipayClient() 显式报错(需你同意,Q2) |
| ⑤ | 🟡 新密钥二次泄露:把新密钥粘到 issue / 聊天 / 文档 | Step 1 第 6 条已明令禁止;本文件全程不含任何真实密钥字符 |
| ⑥ | 🟢 Step 4 重写历史 | 默认不做;若做需你明确授权 force push |
| ⑦ | 🟡 application-dev.example.yml 补块后新人仍可能填错格式 |
私钥必须是 PKCS#8 单行、去掉 -----BEGIN----- 头尾,在模板注释中写明 |
/apply)9021000166699753)是否有其他环境/同事正在使用?轮换会立即打断他们。是否有统一时间窗口?application.yml 的 hotel.alipay.* 采用方案 A(空串默认值,推荐)还是方案 B(无默认值 fail-fast)?若选 A,是否同意配套修改 AlipayConfig.alipayClient(),在密钥为空时显式抛「支付宝支付未配置」而非用空密钥初始化?notify-url 的 natapp 隧道域名(pc948923.natappfree.cc)是否也外提?它与你账号绑定、敏感度中等,但外提后你每次换隧道都要改环境变量。spring.config.import + ~/.forge/secrets/alipay.properties)?好处是不用配环境变量、与仓库既有 crypto.properties 约定同源;代价是需先实测加载顺序。git push --force 到 master,我不会自行执行。我的默认建议是不做(内网仓库 + 已轮换 = 收益低于协作成本)。AGENTS.md 文档修正AGENTS.md 的路径偏差?
cd forge && mvn clean install → 真实为 cd forge-server && mvn clean install(仓库根 forge/ 是空目录)forge/forge-admin-server/... → 真实为 forge-server/forge-admin-server/...application-dev.yml = 本地配置、.example.yml = 可提交模板),错的只是路径前缀Select-String -Pattern "MIIEvg" -Path forge-server\*\src\main\resources\*.yml 零命中(旧私钥前缀已清除)hotel.alipay 块均已占位化,生产块已补齐(修复 2.3 的 P0)application-dev.example.yml 已补 hotel.alipay 空模板 + 格式说明git ls-files | Select-String "application-dev.yml" 零命中(已脱离跟踪)git check-ignore -v forge-server/forge-admin-server/src/main/resources/application-dev.yml 输出 .gitignore:83(规则已生效)application-dev.yml 文件仍存在且内容正确(--cached 未误删)createPay → 收银台 → 回调 → pay_status=1)application-dev.yml本变更不直接改动资金流转逻辑,但涉及密钥安全与破坏性 git 操作(
git rm --cached/ 可能的push --force),按AGENTS.md5.9 与操作准则,须你确认后方可执行。 AI 助手不得代签、不得自行执行 Step 3 之后的任何 git 写操作。
确认范围声明(签署即表示已阅读并认可):
application-dev.yml,已安排通知与备份hotel-pay-mock-hardening 的 HARD-GATE 签署之后再动 6 份共享 yml(风险 ③)