Files
competition-website/scripts/_secret.cjs

113 lines
4.9 KiB
JavaScript
Raw Normal View History

security(config): 移除内置 JWT 密钥明文兜底,收敛到唯一解析入口(R105-D1) 问题(R104 卫生清理 H2 分类的唯二受版本控制命中): config.default.js 原为 const DEFAULT_JWT_SECRET = 'competition_jwt_secret_key_2026_br!cs'; secret: process.env.JWT_SECRET || DEFAULT_JWT_SECRET 该常量为**已入库、任何人可读**的明文,且与 scripts/.creds.local.json 的 jwt_fallback_secret 同值 ⇒ 等价于「JWT 签名密钥公开 ⇒ 任意人可伪造 token 冒充任意身份」。它与仓库既有纪律(_creds.py / _db.cjs / 本文件:459 对 DB_PASSWORD 的「缺失即阻断」)不一致 —— 同族问题各自实现必然漂移。 修法: · config/config.default.js - 删除 DEFAULT_JWT_SECRET 常量与 `||` 兜底,改调唯一解析入口; - 生产安全守卫中同步移除 `jwt.secret === DEFAULT_JWT_SECRET` 比对分支 (不再存在可比的兜底值;缺失时解析期即抛错)。 · 新增 scripts/_secret.cjs(与 _db.cjs / _creds.py 同一纪律) ① env JWT_SECRET → ② scripts/.creds.local.json:jwt_fallback_secret → ③ 明确失败(⛔ 刻意不提供明文兜底) 行为等价性:生产经 systemd EnvironmentFile、本地经 loadDeployEnv 从 deploy/.env.production 注入 JWT_SECRET,正常运行路径逐字不变;仅消除了 「密钥缺失时静默使用公开值」这一分支。 验证: · 后端真实重启 ⇒ 配置加载成功,生效 jwt.secret 指纹与注入值一致; · 端到端判别力(本地 7001 与生产 8023 各一次):新密钥签发 ⇒ 200, 旧密钥签发 ⇒ 401(auth.js jwt.verify 分支); · scripts/verify_no_secrets.py:HIGH 0,判别力自证 17/17; · scripts/verify_script_health.py:H1–H4 PASS。
2026-10-12 10:26:03 +08:00
'use strict';
/**
security(config): 四个同族兜底缺陷一次收口 —— APP_KEYS / 弱口令 / IPv6 兜底 / 本地生产密钥同值(R106-D6~D9) 共同根因(R104 结论的延续):**同族问题各自实现必然漂移**。 本仓 DB_PASSWORD 与 JWT_SECRET(R105-D1)已统一为「缺凭据明确失败」, 但同族的另三处仍停在「可预测的公开兜底」上,另新增一处跨环境密钥同值。 D6 · APP_KEYS 派生兜底(原 config.default.js:157) 原:const DEFAULT_APP_KEYS = appInfo.name + '_competition_website_secret_key_2026'; config.keys = process.env.APP_KEYS || DEFAULT_APP_KEYS; 「派生」不等于安全:由 appInfo.name 可推算 ⇒ 等价于「Cookie 签名密钥公开 ⇒ 任何人可伪造已签名 Cookie」。收口到 _secret.cjs 唯一解析入口: · scripts/_secret.cjs 抽出通用 _loadSecret(),新增 loadAppKeys() (① env → ② scripts/.creds.local.json:app_keys → ③ 明确失败,⛔ 无兜底) · 生产安全守卫移除 config.keys === DEFAULT_APP_KEYS 比对分支(已无可比兜底值) · scripts/_creds.py 与 .creds.local.example.json 同步键面(两侧同源,永不漂移) D7 · 生产弱口令兜底(原 config.default.js:176) 原:config.testDefaultPassword = process.env.TEST_DEFAULT_PASSWORD || '123456'; 而 deploy/.env.production 的 TEST_DEFAULT_PASSWORD 一直是**注释状态** ⇒ 生产 实际取到的就是弱口令 '123456'。一旦走到「批量写默认口令」的路径(重置测试数据 / 显式 testFlag=true 的导入)会批量设弱口令 —— 属**静默弱口令**而非明确失败。 修法:生产(config.isProduction)未显式注入 ⇒ 置 null;使用点改走新增的 app/lib/passwordPolicy.requireTestPassword() 明确失败。测试环境语义逐字不变。 D8 · 本地 DB 兜底 host 引发 IPv6 建连超时(原 config.default.js:217) 原:host: process.env.DB_HOST || 'localhost'。Node >= 18 的 verbatim DNS 可能把 localhost 解析到 ::1,而本机 MariaDB 只听 IPv4 ⇒ **建连 ETIMEDOUT**(不报 1045、 不报 ECONNREFUSED),现象是「后端启动卡在 DB」,极易误判为「服务没起来」。 且本仓 scripts/_db.cjs:43 的兜底早已是 '127.0.0.1' —— 同子系统两套默认值必然漂移。 修法:兜底改为 IPv4 字面量(生产显式注入 DB_HOST ⇒ 逐字不变)。 配套(config.local.js 为 gitignored 本地文件,不入库):补 host/port 覆盖 —— 「第二道保险」原本只保账号/口令,反而漏掉了真正会出问题的连接目标。 D9 · 本地 dev 与生产共用同一 JWT/APP_KEYS(loadDeployEnv) 非生产跳过清单原本只含 DB_/REDIS_/ALIYUN_/SMS_,注释称「仍保留 JWT_SECRET / APP_KEYS …… 本地无副作用」—— 该结论不成立:本地进程持有 **生产签名密钥** ⇒ 本地 token 可冒充生产用户(反之亦然),两套环境的会话边界 实际被打通,属**安全属性**缺陷。 修法:新增 DEPLOY_ENV_LOCAL_SKIP_KEYS 精确跳过(⛔ 不用前缀以免误伤),本地改由 scripts/.creds.local.json 供值;Python 侧 scripts/_fixtures.py resolve_jwt_secret() 同步为「非生产优先本仓凭据文件」—— 否则脚本会用生产密钥签 token 交给本地后端 验签,必然 401(正是该函数注释里记录过的 23 项级联假红)。 行为等价性(生产逐字不变,已逐项实测): · D6/D8:生产显式注入 APP_KEYS / DB_HOST ⇒ 取值路径不变 · D7:生产 resetTestData 本有 403 守卫、导入默认 testFlag=false ⇒ 原不可达 · D9:isProdEnv=true ⇒ 不跳过任何键 ⇒ 生产加载路径不变 验证: · 配置层双环境断言:本地 jwt=8066e07d5683 / keys=e4cfe40ff3ea(≠ 生产 a05591750093 / ae0d155385c1);生产两值逐字不变;生产 testDefaultPassword=null · 本地后端真实重启(仅给 DB_PORT=3307、**不给 DB_HOST**)⇒ 启动成功, /api/health {"code":0,...,"db":"ok"};日志显示跳过 16 个键(含 JWT_SECRET/APP_KEYS) · D9 判决点 d9_verify_split.py:本地密钥签 token ⇒ 200;生产密钥 ⇒ **401** · 生产部署后 r106_prod_sentinel.js:pass=11 fail=0;/api/health 200 · 门禁:verify_no_secrets.py VERIFY_ALL_PASS(HIGH 0,判别力自证 17/17); verify_script_health.py H1–H4 PASS
2026-10-12 11:06:01 +08:00
* 后端配置共用:**配置密钥解析(唯一入口)**
security(config): 移除内置 JWT 密钥明文兜底,收敛到唯一解析入口(R105-D1) 问题(R104 卫生清理 H2 分类的唯二受版本控制命中): config.default.js 原为 const DEFAULT_JWT_SECRET = 'competition_jwt_secret_key_2026_br!cs'; secret: process.env.JWT_SECRET || DEFAULT_JWT_SECRET 该常量为**已入库、任何人可读**的明文,且与 scripts/.creds.local.json 的 jwt_fallback_secret 同值 ⇒ 等价于「JWT 签名密钥公开 ⇒ 任意人可伪造 token 冒充任意身份」。它与仓库既有纪律(_creds.py / _db.cjs / 本文件:459 对 DB_PASSWORD 的「缺失即阻断」)不一致 —— 同族问题各自实现必然漂移。 修法: · config/config.default.js - 删除 DEFAULT_JWT_SECRET 常量与 `||` 兜底,改调唯一解析入口; - 生产安全守卫中同步移除 `jwt.secret === DEFAULT_JWT_SECRET` 比对分支 (不再存在可比的兜底值;缺失时解析期即抛错)。 · 新增 scripts/_secret.cjs(与 _db.cjs / _creds.py 同一纪律) ① env JWT_SECRET → ② scripts/.creds.local.json:jwt_fallback_secret → ③ 明确失败(⛔ 刻意不提供明文兜底) 行为等价性:生产经 systemd EnvironmentFile、本地经 loadDeployEnv 从 deploy/.env.production 注入 JWT_SECRET,正常运行路径逐字不变;仅消除了 「密钥缺失时静默使用公开值」这一分支。 验证: · 后端真实重启 ⇒ 配置加载成功,生效 jwt.secret 指纹与注入值一致; · 端到端判别力(本地 7001 与生产 8023 各一次):新密钥签发 ⇒ 200, 旧密钥签发 ⇒ 401(auth.js jwt.verify 分支); · scripts/verify_no_secrets.py:HIGH 0,判别力自证 17/17; · scripts/verify_script_health.py:H1–H4 PASS。
2026-10-12 10:26:03 +08:00
* ============================================================
security(config): 四个同族兜底缺陷一次收口 —— APP_KEYS / 弱口令 / IPv6 兜底 / 本地生产密钥同值(R106-D6~D9) 共同根因(R104 结论的延续):**同族问题各自实现必然漂移**。 本仓 DB_PASSWORD 与 JWT_SECRET(R105-D1)已统一为「缺凭据明确失败」, 但同族的另三处仍停在「可预测的公开兜底」上,另新增一处跨环境密钥同值。 D6 · APP_KEYS 派生兜底(原 config.default.js:157) 原:const DEFAULT_APP_KEYS = appInfo.name + '_competition_website_secret_key_2026'; config.keys = process.env.APP_KEYS || DEFAULT_APP_KEYS; 「派生」不等于安全:由 appInfo.name 可推算 ⇒ 等价于「Cookie 签名密钥公开 ⇒ 任何人可伪造已签名 Cookie」。收口到 _secret.cjs 唯一解析入口: · scripts/_secret.cjs 抽出通用 _loadSecret(),新增 loadAppKeys() (① env → ② scripts/.creds.local.json:app_keys → ③ 明确失败,⛔ 无兜底) · 生产安全守卫移除 config.keys === DEFAULT_APP_KEYS 比对分支(已无可比兜底值) · scripts/_creds.py 与 .creds.local.example.json 同步键面(两侧同源,永不漂移) D7 · 生产弱口令兜底(原 config.default.js:176) 原:config.testDefaultPassword = process.env.TEST_DEFAULT_PASSWORD || '123456'; 而 deploy/.env.production 的 TEST_DEFAULT_PASSWORD 一直是**注释状态** ⇒ 生产 实际取到的就是弱口令 '123456'。一旦走到「批量写默认口令」的路径(重置测试数据 / 显式 testFlag=true 的导入)会批量设弱口令 —— 属**静默弱口令**而非明确失败。 修法:生产(config.isProduction)未显式注入 ⇒ 置 null;使用点改走新增的 app/lib/passwordPolicy.requireTestPassword() 明确失败。测试环境语义逐字不变。 D8 · 本地 DB 兜底 host 引发 IPv6 建连超时(原 config.default.js:217) 原:host: process.env.DB_HOST || 'localhost'。Node >= 18 的 verbatim DNS 可能把 localhost 解析到 ::1,而本机 MariaDB 只听 IPv4 ⇒ **建连 ETIMEDOUT**(不报 1045、 不报 ECONNREFUSED),现象是「后端启动卡在 DB」,极易误判为「服务没起来」。 且本仓 scripts/_db.cjs:43 的兜底早已是 '127.0.0.1' —— 同子系统两套默认值必然漂移。 修法:兜底改为 IPv4 字面量(生产显式注入 DB_HOST ⇒ 逐字不变)。 配套(config.local.js 为 gitignored 本地文件,不入库):补 host/port 覆盖 —— 「第二道保险」原本只保账号/口令,反而漏掉了真正会出问题的连接目标。 D9 · 本地 dev 与生产共用同一 JWT/APP_KEYS(loadDeployEnv) 非生产跳过清单原本只含 DB_/REDIS_/ALIYUN_/SMS_,注释称「仍保留 JWT_SECRET / APP_KEYS …… 本地无副作用」—— 该结论不成立:本地进程持有 **生产签名密钥** ⇒ 本地 token 可冒充生产用户(反之亦然),两套环境的会话边界 实际被打通,属**安全属性**缺陷。 修法:新增 DEPLOY_ENV_LOCAL_SKIP_KEYS 精确跳过(⛔ 不用前缀以免误伤),本地改由 scripts/.creds.local.json 供值;Python 侧 scripts/_fixtures.py resolve_jwt_secret() 同步为「非生产优先本仓凭据文件」—— 否则脚本会用生产密钥签 token 交给本地后端 验签,必然 401(正是该函数注释里记录过的 23 项级联假红)。 行为等价性(生产逐字不变,已逐项实测): · D6/D8:生产显式注入 APP_KEYS / DB_HOST ⇒ 取值路径不变 · D7:生产 resetTestData 本有 403 守卫、导入默认 testFlag=false ⇒ 原不可达 · D9:isProdEnv=true ⇒ 不跳过任何键 ⇒ 生产加载路径不变 验证: · 配置层双环境断言:本地 jwt=8066e07d5683 / keys=e4cfe40ff3ea(≠ 生产 a05591750093 / ae0d155385c1);生产两值逐字不变;生产 testDefaultPassword=null · 本地后端真实重启(仅给 DB_PORT=3307、**不给 DB_HOST**)⇒ 启动成功, /api/health {"code":0,...,"db":"ok"};日志显示跳过 16 个键(含 JWT_SECRET/APP_KEYS) · D9 判决点 d9_verify_split.py:本地密钥签 token ⇒ 200;生产密钥 ⇒ **401** · 生产部署后 r106_prod_sentinel.js:pass=11 fail=0;/api/health 200 · 门禁:verify_no_secrets.py VERIFY_ALL_PASS(HIGH 0,判别力自证 17/17); verify_script_health.py H1–H4 PASS
2026-10-12 11:06:01 +08:00
* 当前覆盖两个同族密钥:
* · `JWT_SECRET` —— JWT 签名密钥(`loadJwtSecret`)
* · `APP_KEYS` —— Cookie 签名密钥(`loadAppKeys`,Egg `config.keys`)
security(config): 移除内置 JWT 密钥明文兜底,收敛到唯一解析入口(R105-D1) 问题(R104 卫生清理 H2 分类的唯二受版本控制命中): config.default.js 原为 const DEFAULT_JWT_SECRET = 'competition_jwt_secret_key_2026_br!cs'; secret: process.env.JWT_SECRET || DEFAULT_JWT_SECRET 该常量为**已入库、任何人可读**的明文,且与 scripts/.creds.local.json 的 jwt_fallback_secret 同值 ⇒ 等价于「JWT 签名密钥公开 ⇒ 任意人可伪造 token 冒充任意身份」。它与仓库既有纪律(_creds.py / _db.cjs / 本文件:459 对 DB_PASSWORD 的「缺失即阻断」)不一致 —— 同族问题各自实现必然漂移。 修法: · config/config.default.js - 删除 DEFAULT_JWT_SECRET 常量与 `||` 兜底,改调唯一解析入口; - 生产安全守卫中同步移除 `jwt.secret === DEFAULT_JWT_SECRET` 比对分支 (不再存在可比的兜底值;缺失时解析期即抛错)。 · 新增 scripts/_secret.cjs(与 _db.cjs / _creds.py 同一纪律) ① env JWT_SECRET → ② scripts/.creds.local.json:jwt_fallback_secret → ③ 明确失败(⛔ 刻意不提供明文兜底) 行为等价性:生产经 systemd EnvironmentFile、本地经 loadDeployEnv 从 deploy/.env.production 注入 JWT_SECRET,正常运行路径逐字不变;仅消除了 「密钥缺失时静默使用公开值」这一分支。 验证: · 后端真实重启 ⇒ 配置加载成功,生效 jwt.secret 指纹与注入值一致; · 端到端判别力(本地 7001 与生产 8023 各一次):新密钥签发 ⇒ 200, 旧密钥签发 ⇒ 401(auth.js jwt.verify 分支); · scripts/verify_no_secrets.py:HIGH 0,判别力自证 17/17; · scripts/verify_script_health.py:H1–H4 PASS。
2026-10-12 10:26:03 +08:00
*
security(config): 四个同族兜底缺陷一次收口 —— APP_KEYS / 弱口令 / IPv6 兜底 / 本地生产密钥同值(R106-D6~D9) 共同根因(R104 结论的延续):**同族问题各自实现必然漂移**。 本仓 DB_PASSWORD 与 JWT_SECRET(R105-D1)已统一为「缺凭据明确失败」, 但同族的另三处仍停在「可预测的公开兜底」上,另新增一处跨环境密钥同值。 D6 · APP_KEYS 派生兜底(原 config.default.js:157) 原:const DEFAULT_APP_KEYS = appInfo.name + '_competition_website_secret_key_2026'; config.keys = process.env.APP_KEYS || DEFAULT_APP_KEYS; 「派生」不等于安全:由 appInfo.name 可推算 ⇒ 等价于「Cookie 签名密钥公开 ⇒ 任何人可伪造已签名 Cookie」。收口到 _secret.cjs 唯一解析入口: · scripts/_secret.cjs 抽出通用 _loadSecret(),新增 loadAppKeys() (① env → ② scripts/.creds.local.json:app_keys → ③ 明确失败,⛔ 无兜底) · 生产安全守卫移除 config.keys === DEFAULT_APP_KEYS 比对分支(已无可比兜底值) · scripts/_creds.py 与 .creds.local.example.json 同步键面(两侧同源,永不漂移) D7 · 生产弱口令兜底(原 config.default.js:176) 原:config.testDefaultPassword = process.env.TEST_DEFAULT_PASSWORD || '123456'; 而 deploy/.env.production 的 TEST_DEFAULT_PASSWORD 一直是**注释状态** ⇒ 生产 实际取到的就是弱口令 '123456'。一旦走到「批量写默认口令」的路径(重置测试数据 / 显式 testFlag=true 的导入)会批量设弱口令 —— 属**静默弱口令**而非明确失败。 修法:生产(config.isProduction)未显式注入 ⇒ 置 null;使用点改走新增的 app/lib/passwordPolicy.requireTestPassword() 明确失败。测试环境语义逐字不变。 D8 · 本地 DB 兜底 host 引发 IPv6 建连超时(原 config.default.js:217) 原:host: process.env.DB_HOST || 'localhost'。Node >= 18 的 verbatim DNS 可能把 localhost 解析到 ::1,而本机 MariaDB 只听 IPv4 ⇒ **建连 ETIMEDOUT**(不报 1045、 不报 ECONNREFUSED),现象是「后端启动卡在 DB」,极易误判为「服务没起来」。 且本仓 scripts/_db.cjs:43 的兜底早已是 '127.0.0.1' —— 同子系统两套默认值必然漂移。 修法:兜底改为 IPv4 字面量(生产显式注入 DB_HOST ⇒ 逐字不变)。 配套(config.local.js 为 gitignored 本地文件,不入库):补 host/port 覆盖 —— 「第二道保险」原本只保账号/口令,反而漏掉了真正会出问题的连接目标。 D9 · 本地 dev 与生产共用同一 JWT/APP_KEYS(loadDeployEnv) 非生产跳过清单原本只含 DB_/REDIS_/ALIYUN_/SMS_,注释称「仍保留 JWT_SECRET / APP_KEYS …… 本地无副作用」—— 该结论不成立:本地进程持有 **生产签名密钥** ⇒ 本地 token 可冒充生产用户(反之亦然),两套环境的会话边界 实际被打通,属**安全属性**缺陷。 修法:新增 DEPLOY_ENV_LOCAL_SKIP_KEYS 精确跳过(⛔ 不用前缀以免误伤),本地改由 scripts/.creds.local.json 供值;Python 侧 scripts/_fixtures.py resolve_jwt_secret() 同步为「非生产优先本仓凭据文件」—— 否则脚本会用生产密钥签 token 交给本地后端 验签,必然 401(正是该函数注释里记录过的 23 项级联假红)。 行为等价性(生产逐字不变,已逐项实测): · D6/D8:生产显式注入 APP_KEYS / DB_HOST ⇒ 取值路径不变 · D7:生产 resetTestData 本有 403 守卫、导入默认 testFlag=false ⇒ 原不可达 · D9:isProdEnv=true ⇒ 不跳过任何键 ⇒ 生产加载路径不变 验证: · 配置层双环境断言:本地 jwt=8066e07d5683 / keys=e4cfe40ff3ea(≠ 生产 a05591750093 / ae0d155385c1);生产两值逐字不变;生产 testDefaultPassword=null · 本地后端真实重启(仅给 DB_PORT=3307、**不给 DB_HOST**)⇒ 启动成功, /api/health {"code":0,...,"db":"ok"};日志显示跳过 16 个键(含 JWT_SECRET/APP_KEYS) · D9 判决点 d9_verify_split.py:本地密钥签 token ⇒ 200;生产密钥 ⇒ **401** · 生产部署后 r106_prod_sentinel.js:pass=11 fail=0;/api/health 200 · 门禁:verify_no_secrets.py VERIFY_ALL_PASS(HIGH 0,判别力自证 17/17); verify_script_health.py H1–H4 PASS
2026-10-12 11:06:01 +08:00
* 为什么单独成文件(R105-D1 + R106-D6,2026-10-12)
security(config): 移除内置 JWT 密钥明文兜底,收敛到唯一解析入口(R105-D1) 问题(R104 卫生清理 H2 分类的唯二受版本控制命中): config.default.js 原为 const DEFAULT_JWT_SECRET = 'competition_jwt_secret_key_2026_br!cs'; secret: process.env.JWT_SECRET || DEFAULT_JWT_SECRET 该常量为**已入库、任何人可读**的明文,且与 scripts/.creds.local.json 的 jwt_fallback_secret 同值 ⇒ 等价于「JWT 签名密钥公开 ⇒ 任意人可伪造 token 冒充任意身份」。它与仓库既有纪律(_creds.py / _db.cjs / 本文件:459 对 DB_PASSWORD 的「缺失即阻断」)不一致 —— 同族问题各自实现必然漂移。 修法: · config/config.default.js - 删除 DEFAULT_JWT_SECRET 常量与 `||` 兜底,改调唯一解析入口; - 生产安全守卫中同步移除 `jwt.secret === DEFAULT_JWT_SECRET` 比对分支 (不再存在可比的兜底值;缺失时解析期即抛错)。 · 新增 scripts/_secret.cjs(与 _db.cjs / _creds.py 同一纪律) ① env JWT_SECRET → ② scripts/.creds.local.json:jwt_fallback_secret → ③ 明确失败(⛔ 刻意不提供明文兜底) 行为等价性:生产经 systemd EnvironmentFile、本地经 loadDeployEnv 从 deploy/.env.production 注入 JWT_SECRET,正常运行路径逐字不变;仅消除了 「密钥缺失时静默使用公开值」这一分支。 验证: · 后端真实重启 ⇒ 配置加载成功,生效 jwt.secret 指纹与注入值一致; · 端到端判别力(本地 7001 与生产 8023 各一次):新密钥签发 ⇒ 200, 旧密钥签发 ⇒ 401(auth.js jwt.verify 分支); · scripts/verify_no_secrets.py:HIGH 0,判别力自证 17/17; · scripts/verify_script_health.py:H1–H4 PASS。
2026-10-12 10:26:03 +08:00
* ------------------------------------------------------------
security(config): 四个同族兜底缺陷一次收口 —— APP_KEYS / 弱口令 / IPv6 兜底 / 本地生产密钥同值(R106-D6~D9) 共同根因(R104 结论的延续):**同族问题各自实现必然漂移**。 本仓 DB_PASSWORD 与 JWT_SECRET(R105-D1)已统一为「缺凭据明确失败」, 但同族的另三处仍停在「可预测的公开兜底」上,另新增一处跨环境密钥同值。 D6 · APP_KEYS 派生兜底(原 config.default.js:157) 原:const DEFAULT_APP_KEYS = appInfo.name + '_competition_website_secret_key_2026'; config.keys = process.env.APP_KEYS || DEFAULT_APP_KEYS; 「派生」不等于安全:由 appInfo.name 可推算 ⇒ 等价于「Cookie 签名密钥公开 ⇒ 任何人可伪造已签名 Cookie」。收口到 _secret.cjs 唯一解析入口: · scripts/_secret.cjs 抽出通用 _loadSecret(),新增 loadAppKeys() (① env → ② scripts/.creds.local.json:app_keys → ③ 明确失败,⛔ 无兜底) · 生产安全守卫移除 config.keys === DEFAULT_APP_KEYS 比对分支(已无可比兜底值) · scripts/_creds.py 与 .creds.local.example.json 同步键面(两侧同源,永不漂移) D7 · 生产弱口令兜底(原 config.default.js:176) 原:config.testDefaultPassword = process.env.TEST_DEFAULT_PASSWORD || '123456'; 而 deploy/.env.production 的 TEST_DEFAULT_PASSWORD 一直是**注释状态** ⇒ 生产 实际取到的就是弱口令 '123456'。一旦走到「批量写默认口令」的路径(重置测试数据 / 显式 testFlag=true 的导入)会批量设弱口令 —— 属**静默弱口令**而非明确失败。 修法:生产(config.isProduction)未显式注入 ⇒ 置 null;使用点改走新增的 app/lib/passwordPolicy.requireTestPassword() 明确失败。测试环境语义逐字不变。 D8 · 本地 DB 兜底 host 引发 IPv6 建连超时(原 config.default.js:217) 原:host: process.env.DB_HOST || 'localhost'。Node >= 18 的 verbatim DNS 可能把 localhost 解析到 ::1,而本机 MariaDB 只听 IPv4 ⇒ **建连 ETIMEDOUT**(不报 1045、 不报 ECONNREFUSED),现象是「后端启动卡在 DB」,极易误判为「服务没起来」。 且本仓 scripts/_db.cjs:43 的兜底早已是 '127.0.0.1' —— 同子系统两套默认值必然漂移。 修法:兜底改为 IPv4 字面量(生产显式注入 DB_HOST ⇒ 逐字不变)。 配套(config.local.js 为 gitignored 本地文件,不入库):补 host/port 覆盖 —— 「第二道保险」原本只保账号/口令,反而漏掉了真正会出问题的连接目标。 D9 · 本地 dev 与生产共用同一 JWT/APP_KEYS(loadDeployEnv) 非生产跳过清单原本只含 DB_/REDIS_/ALIYUN_/SMS_,注释称「仍保留 JWT_SECRET / APP_KEYS …… 本地无副作用」—— 该结论不成立:本地进程持有 **生产签名密钥** ⇒ 本地 token 可冒充生产用户(反之亦然),两套环境的会话边界 实际被打通,属**安全属性**缺陷。 修法:新增 DEPLOY_ENV_LOCAL_SKIP_KEYS 精确跳过(⛔ 不用前缀以免误伤),本地改由 scripts/.creds.local.json 供值;Python 侧 scripts/_fixtures.py resolve_jwt_secret() 同步为「非生产优先本仓凭据文件」—— 否则脚本会用生产密钥签 token 交给本地后端 验签,必然 401(正是该函数注释里记录过的 23 项级联假红)。 行为等价性(生产逐字不变,已逐项实测): · D6/D8:生产显式注入 APP_KEYS / DB_HOST ⇒ 取值路径不变 · D7:生产 resetTestData 本有 403 守卫、导入默认 testFlag=false ⇒ 原不可达 · D9:isProdEnv=true ⇒ 不跳过任何键 ⇒ 生产加载路径不变 验证: · 配置层双环境断言:本地 jwt=8066e07d5683 / keys=e4cfe40ff3ea(≠ 生产 a05591750093 / ae0d155385c1);生产两值逐字不变;生产 testDefaultPassword=null · 本地后端真实重启(仅给 DB_PORT=3307、**不给 DB_HOST**)⇒ 启动成功, /api/health {"code":0,...,"db":"ok"};日志显示跳过 16 个键(含 JWT_SECRET/APP_KEYS) · D9 判决点 d9_verify_split.py:本地密钥签 token ⇒ 200;生产密钥 ⇒ **401** · 生产部署后 r106_prod_sentinel.js:pass=11 fail=0;/api/health 200 · 门禁:verify_no_secrets.py VERIFY_ALL_PASS(HIGH 0,判别力自证 17/17); verify_script_health.py H1–H4 PASS
2026-10-12 11:06:01 +08:00
* 原实现(config.default.js 旧 L300/L304 与 L157/L158)各自带**可预测的兜底值**:
security(config): 移除内置 JWT 密钥明文兜底,收敛到唯一解析入口(R105-D1) 问题(R104 卫生清理 H2 分类的唯二受版本控制命中): config.default.js 原为 const DEFAULT_JWT_SECRET = 'competition_jwt_secret_key_2026_br!cs'; secret: process.env.JWT_SECRET || DEFAULT_JWT_SECRET 该常量为**已入库、任何人可读**的明文,且与 scripts/.creds.local.json 的 jwt_fallback_secret 同值 ⇒ 等价于「JWT 签名密钥公开 ⇒ 任意人可伪造 token 冒充任意身份」。它与仓库既有纪律(_creds.py / _db.cjs / 本文件:459 对 DB_PASSWORD 的「缺失即阻断」)不一致 —— 同族问题各自实现必然漂移。 修法: · config/config.default.js - 删除 DEFAULT_JWT_SECRET 常量与 `||` 兜底,改调唯一解析入口; - 生产安全守卫中同步移除 `jwt.secret === DEFAULT_JWT_SECRET` 比对分支 (不再存在可比的兜底值;缺失时解析期即抛错)。 · 新增 scripts/_secret.cjs(与 _db.cjs / _creds.py 同一纪律) ① env JWT_SECRET → ② scripts/.creds.local.json:jwt_fallback_secret → ③ 明确失败(⛔ 刻意不提供明文兜底) 行为等价性:生产经 systemd EnvironmentFile、本地经 loadDeployEnv 从 deploy/.env.production 注入 JWT_SECRET,正常运行路径逐字不变;仅消除了 「密钥缺失时静默使用公开值」这一分支。 验证: · 后端真实重启 ⇒ 配置加载成功,生效 jwt.secret 指纹与注入值一致; · 端到端判别力(本地 7001 与生产 8023 各一次):新密钥签发 ⇒ 200, 旧密钥签发 ⇒ 401(auth.js jwt.verify 分支); · scripts/verify_no_secrets.py:HIGH 0,判别力自证 17/17; · scripts/verify_script_health.py:H1–H4 PASS。
2026-10-12 10:26:03 +08:00
* const DEFAULT_JWT_SECRET = '<32 位明文串>';
* secret: process.env.JWT_SECRET || DEFAULT_JWT_SECRET
security(config): 四个同族兜底缺陷一次收口 —— APP_KEYS / 弱口令 / IPv6 兜底 / 本地生产密钥同值(R106-D6~D9) 共同根因(R104 结论的延续):**同族问题各自实现必然漂移**。 本仓 DB_PASSWORD 与 JWT_SECRET(R105-D1)已统一为「缺凭据明确失败」, 但同族的另三处仍停在「可预测的公开兜底」上,另新增一处跨环境密钥同值。 D6 · APP_KEYS 派生兜底(原 config.default.js:157) 原:const DEFAULT_APP_KEYS = appInfo.name + '_competition_website_secret_key_2026'; config.keys = process.env.APP_KEYS || DEFAULT_APP_KEYS; 「派生」不等于安全:由 appInfo.name 可推算 ⇒ 等价于「Cookie 签名密钥公开 ⇒ 任何人可伪造已签名 Cookie」。收口到 _secret.cjs 唯一解析入口: · scripts/_secret.cjs 抽出通用 _loadSecret(),新增 loadAppKeys() (① env → ② scripts/.creds.local.json:app_keys → ③ 明确失败,⛔ 无兜底) · 生产安全守卫移除 config.keys === DEFAULT_APP_KEYS 比对分支(已无可比兜底值) · scripts/_creds.py 与 .creds.local.example.json 同步键面(两侧同源,永不漂移) D7 · 生产弱口令兜底(原 config.default.js:176) 原:config.testDefaultPassword = process.env.TEST_DEFAULT_PASSWORD || '123456'; 而 deploy/.env.production 的 TEST_DEFAULT_PASSWORD 一直是**注释状态** ⇒ 生产 实际取到的就是弱口令 '123456'。一旦走到「批量写默认口令」的路径(重置测试数据 / 显式 testFlag=true 的导入)会批量设弱口令 —— 属**静默弱口令**而非明确失败。 修法:生产(config.isProduction)未显式注入 ⇒ 置 null;使用点改走新增的 app/lib/passwordPolicy.requireTestPassword() 明确失败。测试环境语义逐字不变。 D8 · 本地 DB 兜底 host 引发 IPv6 建连超时(原 config.default.js:217) 原:host: process.env.DB_HOST || 'localhost'。Node >= 18 的 verbatim DNS 可能把 localhost 解析到 ::1,而本机 MariaDB 只听 IPv4 ⇒ **建连 ETIMEDOUT**(不报 1045、 不报 ECONNREFUSED),现象是「后端启动卡在 DB」,极易误判为「服务没起来」。 且本仓 scripts/_db.cjs:43 的兜底早已是 '127.0.0.1' —— 同子系统两套默认值必然漂移。 修法:兜底改为 IPv4 字面量(生产显式注入 DB_HOST ⇒ 逐字不变)。 配套(config.local.js 为 gitignored 本地文件,不入库):补 host/port 覆盖 —— 「第二道保险」原本只保账号/口令,反而漏掉了真正会出问题的连接目标。 D9 · 本地 dev 与生产共用同一 JWT/APP_KEYS(loadDeployEnv) 非生产跳过清单原本只含 DB_/REDIS_/ALIYUN_/SMS_,注释称「仍保留 JWT_SECRET / APP_KEYS …… 本地无副作用」—— 该结论不成立:本地进程持有 **生产签名密钥** ⇒ 本地 token 可冒充生产用户(反之亦然),两套环境的会话边界 实际被打通,属**安全属性**缺陷。 修法:新增 DEPLOY_ENV_LOCAL_SKIP_KEYS 精确跳过(⛔ 不用前缀以免误伤),本地改由 scripts/.creds.local.json 供值;Python 侧 scripts/_fixtures.py resolve_jwt_secret() 同步为「非生产优先本仓凭据文件」—— 否则脚本会用生产密钥签 token 交给本地后端 验签,必然 401(正是该函数注释里记录过的 23 项级联假红)。 行为等价性(生产逐字不变,已逐项实测): · D6/D8:生产显式注入 APP_KEYS / DB_HOST ⇒ 取值路径不变 · D7:生产 resetTestData 本有 403 守卫、导入默认 testFlag=false ⇒ 原不可达 · D9:isProdEnv=true ⇒ 不跳过任何键 ⇒ 生产加载路径不变 验证: · 配置层双环境断言:本地 jwt=8066e07d5683 / keys=e4cfe40ff3ea(≠ 生产 a05591750093 / ae0d155385c1);生产两值逐字不变;生产 testDefaultPassword=null · 本地后端真实重启(仅给 DB_PORT=3307、**不给 DB_HOST**)⇒ 启动成功, /api/health {"code":0,...,"db":"ok"};日志显示跳过 16 个键(含 JWT_SECRET/APP_KEYS) · D9 判决点 d9_verify_split.py:本地密钥签 token ⇒ 200;生产密钥 ⇒ **401** · 生产部署后 r106_prod_sentinel.js:pass=11 fail=0;/api/health 200 · 门禁:verify_no_secrets.py VERIFY_ALL_PASS(HIGH 0,判别力自证 17/17); verify_script_health.py H1–H4 PASS
2026-10-12 11:06:01 +08:00
* const DEFAULT_APP_KEYS = appInfo.name + '_competition_website_secret_key_2026';
* config.keys = process.env.APP_KEYS || DEFAULT_APP_KEYS
* 两者都是「**已入库、任何人可读或可推算**」的值 ⇒ 等价于「密钥公开」:
* · JWT_SECRET 公开 ⇒ 任意人可伪造 token 冒充任意身份;
* · APP_KEYS 公开 ⇒ 任意人可伪造已签名 Cookie(session / 防篡改校验)。
* 且它们与仓库既有纪律直接冲突:
security(config): 移除内置 JWT 密钥明文兜底,收敛到唯一解析入口(R105-D1) 问题(R104 卫生清理 H2 分类的唯二受版本控制命中): config.default.js 原为 const DEFAULT_JWT_SECRET = 'competition_jwt_secret_key_2026_br!cs'; secret: process.env.JWT_SECRET || DEFAULT_JWT_SECRET 该常量为**已入库、任何人可读**的明文,且与 scripts/.creds.local.json 的 jwt_fallback_secret 同值 ⇒ 等价于「JWT 签名密钥公开 ⇒ 任意人可伪造 token 冒充任意身份」。它与仓库既有纪律(_creds.py / _db.cjs / 本文件:459 对 DB_PASSWORD 的「缺失即阻断」)不一致 —— 同族问题各自实现必然漂移。 修法: · config/config.default.js - 删除 DEFAULT_JWT_SECRET 常量与 `||` 兜底,改调唯一解析入口; - 生产安全守卫中同步移除 `jwt.secret === DEFAULT_JWT_SECRET` 比对分支 (不再存在可比的兜底值;缺失时解析期即抛错)。 · 新增 scripts/_secret.cjs(与 _db.cjs / _creds.py 同一纪律) ① env JWT_SECRET → ② scripts/.creds.local.json:jwt_fallback_secret → ③ 明确失败(⛔ 刻意不提供明文兜底) 行为等价性:生产经 systemd EnvironmentFile、本地经 loadDeployEnv 从 deploy/.env.production 注入 JWT_SECRET,正常运行路径逐字不变;仅消除了 「密钥缺失时静默使用公开值」这一分支。 验证: · 后端真实重启 ⇒ 配置加载成功,生效 jwt.secret 指纹与注入值一致; · 端到端判别力(本地 7001 与生产 8023 各一次):新密钥签发 ⇒ 200, 旧密钥签发 ⇒ 401(auth.js jwt.verify 分支); · scripts/verify_no_secrets.py:HIGH 0,判别力自证 17/17; · scripts/verify_script_health.py:H1–H4 PASS。
2026-10-12 10:26:03 +08:00
* · `scripts/_creds.py` / `backend/scripts/_db.cjs` 早已统一为「缺凭据明确失败」;
security(config): 四个同族兜底缺陷一次收口 —— APP_KEYS / 弱口令 / IPv6 兜底 / 本地生产密钥同值(R106-D6~D9) 共同根因(R104 结论的延续):**同族问题各自实现必然漂移**。 本仓 DB_PASSWORD 与 JWT_SECRET(R105-D1)已统一为「缺凭据明确失败」, 但同族的另三处仍停在「可预测的公开兜底」上,另新增一处跨环境密钥同值。 D6 · APP_KEYS 派生兜底(原 config.default.js:157) 原:const DEFAULT_APP_KEYS = appInfo.name + '_competition_website_secret_key_2026'; config.keys = process.env.APP_KEYS || DEFAULT_APP_KEYS; 「派生」不等于安全:由 appInfo.name 可推算 ⇒ 等价于「Cookie 签名密钥公开 ⇒ 任何人可伪造已签名 Cookie」。收口到 _secret.cjs 唯一解析入口: · scripts/_secret.cjs 抽出通用 _loadSecret(),新增 loadAppKeys() (① env → ② scripts/.creds.local.json:app_keys → ③ 明确失败,⛔ 无兜底) · 生产安全守卫移除 config.keys === DEFAULT_APP_KEYS 比对分支(已无可比兜底值) · scripts/_creds.py 与 .creds.local.example.json 同步键面(两侧同源,永不漂移) D7 · 生产弱口令兜底(原 config.default.js:176) 原:config.testDefaultPassword = process.env.TEST_DEFAULT_PASSWORD || '123456'; 而 deploy/.env.production 的 TEST_DEFAULT_PASSWORD 一直是**注释状态** ⇒ 生产 实际取到的就是弱口令 '123456'。一旦走到「批量写默认口令」的路径(重置测试数据 / 显式 testFlag=true 的导入)会批量设弱口令 —— 属**静默弱口令**而非明确失败。 修法:生产(config.isProduction)未显式注入 ⇒ 置 null;使用点改走新增的 app/lib/passwordPolicy.requireTestPassword() 明确失败。测试环境语义逐字不变。 D8 · 本地 DB 兜底 host 引发 IPv6 建连超时(原 config.default.js:217) 原:host: process.env.DB_HOST || 'localhost'。Node >= 18 的 verbatim DNS 可能把 localhost 解析到 ::1,而本机 MariaDB 只听 IPv4 ⇒ **建连 ETIMEDOUT**(不报 1045、 不报 ECONNREFUSED),现象是「后端启动卡在 DB」,极易误判为「服务没起来」。 且本仓 scripts/_db.cjs:43 的兜底早已是 '127.0.0.1' —— 同子系统两套默认值必然漂移。 修法:兜底改为 IPv4 字面量(生产显式注入 DB_HOST ⇒ 逐字不变)。 配套(config.local.js 为 gitignored 本地文件,不入库):补 host/port 覆盖 —— 「第二道保险」原本只保账号/口令,反而漏掉了真正会出问题的连接目标。 D9 · 本地 dev 与生产共用同一 JWT/APP_KEYS(loadDeployEnv) 非生产跳过清单原本只含 DB_/REDIS_/ALIYUN_/SMS_,注释称「仍保留 JWT_SECRET / APP_KEYS …… 本地无副作用」—— 该结论不成立:本地进程持有 **生产签名密钥** ⇒ 本地 token 可冒充生产用户(反之亦然),两套环境的会话边界 实际被打通,属**安全属性**缺陷。 修法:新增 DEPLOY_ENV_LOCAL_SKIP_KEYS 精确跳过(⛔ 不用前缀以免误伤),本地改由 scripts/.creds.local.json 供值;Python 侧 scripts/_fixtures.py resolve_jwt_secret() 同步为「非生产优先本仓凭据文件」—— 否则脚本会用生产密钥签 token 交给本地后端 验签,必然 401(正是该函数注释里记录过的 23 项级联假红)。 行为等价性(生产逐字不变,已逐项实测): · D6/D8:生产显式注入 APP_KEYS / DB_HOST ⇒ 取值路径不变 · D7:生产 resetTestData 本有 403 守卫、导入默认 testFlag=false ⇒ 原不可达 · D9:isProdEnv=true ⇒ 不跳过任何键 ⇒ 生产加载路径不变 验证: · 配置层双环境断言:本地 jwt=8066e07d5683 / keys=e4cfe40ff3ea(≠ 生产 a05591750093 / ae0d155385c1);生产两值逐字不变;生产 testDefaultPassword=null · 本地后端真实重启(仅给 DB_PORT=3307、**不给 DB_HOST**)⇒ 启动成功, /api/health {"code":0,...,"db":"ok"};日志显示跳过 16 个键(含 JWT_SECRET/APP_KEYS) · D9 判决点 d9_verify_split.py:本地密钥签 token ⇒ 200;生产密钥 ⇒ **401** · 生产部署后 r106_prod_sentinel.js:pass=11 fail=0;/api/health 200 · 门禁:verify_no_secrets.py VERIFY_ALL_PASS(HIGH 0,判别力自证 17/17); verify_script_health.py H1–H4 PASS
2026-10-12 11:06:01 +08:00
* · config.default.js 对 `DB_PASSWORD` 也已改为「缺失即阻断」。
* 同族问题若各自实现必然漂移(R104 的结论)⇒ 收敛到**唯一解析源**,不留兜底。
security(config): 移除内置 JWT 密钥明文兜底,收敛到唯一解析入口(R105-D1) 问题(R104 卫生清理 H2 分类的唯二受版本控制命中): config.default.js 原为 const DEFAULT_JWT_SECRET = 'competition_jwt_secret_key_2026_br!cs'; secret: process.env.JWT_SECRET || DEFAULT_JWT_SECRET 该常量为**已入库、任何人可读**的明文,且与 scripts/.creds.local.json 的 jwt_fallback_secret 同值 ⇒ 等价于「JWT 签名密钥公开 ⇒ 任意人可伪造 token 冒充任意身份」。它与仓库既有纪律(_creds.py / _db.cjs / 本文件:459 对 DB_PASSWORD 的「缺失即阻断」)不一致 —— 同族问题各自实现必然漂移。 修法: · config/config.default.js - 删除 DEFAULT_JWT_SECRET 常量与 `||` 兜底,改调唯一解析入口; - 生产安全守卫中同步移除 `jwt.secret === DEFAULT_JWT_SECRET` 比对分支 (不再存在可比的兜底值;缺失时解析期即抛错)。 · 新增 scripts/_secret.cjs(与 _db.cjs / _creds.py 同一纪律) ① env JWT_SECRET → ② scripts/.creds.local.json:jwt_fallback_secret → ③ 明确失败(⛔ 刻意不提供明文兜底) 行为等价性:生产经 systemd EnvironmentFile、本地经 loadDeployEnv 从 deploy/.env.production 注入 JWT_SECRET,正常运行路径逐字不变;仅消除了 「密钥缺失时静默使用公开值」这一分支。 验证: · 后端真实重启 ⇒ 配置加载成功,生效 jwt.secret 指纹与注入值一致; · 端到端判别力(本地 7001 与生产 8023 各一次):新密钥签发 ⇒ 200, 旧密钥签发 ⇒ 401(auth.js jwt.verify 分支); · scripts/verify_no_secrets.py:HIGH 0,判别力自证 17/17; · scripts/verify_script_health.py:H1–H4 PASS。
2026-10-12 10:26:03 +08:00
*
* 读取优先级(与 `_db.cjs` / `_creds.py` 同一纪律)
* ------------------------------------------------------------
security(config): 四个同族兜底缺陷一次收口 —— APP_KEYS / 弱口令 / IPv6 兜底 / 本地生产密钥同值(R106-D6~D9) 共同根因(R104 结论的延续):**同族问题各自实现必然漂移**。 本仓 DB_PASSWORD 与 JWT_SECRET(R105-D1)已统一为「缺凭据明确失败」, 但同族的另三处仍停在「可预测的公开兜底」上,另新增一处跨环境密钥同值。 D6 · APP_KEYS 派生兜底(原 config.default.js:157) 原:const DEFAULT_APP_KEYS = appInfo.name + '_competition_website_secret_key_2026'; config.keys = process.env.APP_KEYS || DEFAULT_APP_KEYS; 「派生」不等于安全:由 appInfo.name 可推算 ⇒ 等价于「Cookie 签名密钥公开 ⇒ 任何人可伪造已签名 Cookie」。收口到 _secret.cjs 唯一解析入口: · scripts/_secret.cjs 抽出通用 _loadSecret(),新增 loadAppKeys() (① env → ② scripts/.creds.local.json:app_keys → ③ 明确失败,⛔ 无兜底) · 生产安全守卫移除 config.keys === DEFAULT_APP_KEYS 比对分支(已无可比兜底值) · scripts/_creds.py 与 .creds.local.example.json 同步键面(两侧同源,永不漂移) D7 · 生产弱口令兜底(原 config.default.js:176) 原:config.testDefaultPassword = process.env.TEST_DEFAULT_PASSWORD || '123456'; 而 deploy/.env.production 的 TEST_DEFAULT_PASSWORD 一直是**注释状态** ⇒ 生产 实际取到的就是弱口令 '123456'。一旦走到「批量写默认口令」的路径(重置测试数据 / 显式 testFlag=true 的导入)会批量设弱口令 —— 属**静默弱口令**而非明确失败。 修法:生产(config.isProduction)未显式注入 ⇒ 置 null;使用点改走新增的 app/lib/passwordPolicy.requireTestPassword() 明确失败。测试环境语义逐字不变。 D8 · 本地 DB 兜底 host 引发 IPv6 建连超时(原 config.default.js:217) 原:host: process.env.DB_HOST || 'localhost'。Node >= 18 的 verbatim DNS 可能把 localhost 解析到 ::1,而本机 MariaDB 只听 IPv4 ⇒ **建连 ETIMEDOUT**(不报 1045、 不报 ECONNREFUSED),现象是「后端启动卡在 DB」,极易误判为「服务没起来」。 且本仓 scripts/_db.cjs:43 的兜底早已是 '127.0.0.1' —— 同子系统两套默认值必然漂移。 修法:兜底改为 IPv4 字面量(生产显式注入 DB_HOST ⇒ 逐字不变)。 配套(config.local.js 为 gitignored 本地文件,不入库):补 host/port 覆盖 —— 「第二道保险」原本只保账号/口令,反而漏掉了真正会出问题的连接目标。 D9 · 本地 dev 与生产共用同一 JWT/APP_KEYS(loadDeployEnv) 非生产跳过清单原本只含 DB_/REDIS_/ALIYUN_/SMS_,注释称「仍保留 JWT_SECRET / APP_KEYS …… 本地无副作用」—— 该结论不成立:本地进程持有 **生产签名密钥** ⇒ 本地 token 可冒充生产用户(反之亦然),两套环境的会话边界 实际被打通,属**安全属性**缺陷。 修法:新增 DEPLOY_ENV_LOCAL_SKIP_KEYS 精确跳过(⛔ 不用前缀以免误伤),本地改由 scripts/.creds.local.json 供值;Python 侧 scripts/_fixtures.py resolve_jwt_secret() 同步为「非生产优先本仓凭据文件」—— 否则脚本会用生产密钥签 token 交给本地后端 验签,必然 401(正是该函数注释里记录过的 23 项级联假红)。 行为等价性(生产逐字不变,已逐项实测): · D6/D8:生产显式注入 APP_KEYS / DB_HOST ⇒ 取值路径不变 · D7:生产 resetTestData 本有 403 守卫、导入默认 testFlag=false ⇒ 原不可达 · D9:isProdEnv=true ⇒ 不跳过任何键 ⇒ 生产加载路径不变 验证: · 配置层双环境断言:本地 jwt=8066e07d5683 / keys=e4cfe40ff3ea(≠ 生产 a05591750093 / ae0d155385c1);生产两值逐字不变;生产 testDefaultPassword=null · 本地后端真实重启(仅给 DB_PORT=3307、**不给 DB_HOST**)⇒ 启动成功, /api/health {"code":0,...,"db":"ok"};日志显示跳过 16 个键(含 JWT_SECRET/APP_KEYS) · D9 判决点 d9_verify_split.py:本地密钥签 token ⇒ 200;生产密钥 ⇒ **401** · 生产部署后 r106_prod_sentinel.js:pass=11 fail=0;/api/health 200 · 门禁:verify_no_secrets.py VERIFY_ALL_PASS(HIGH 0,判别力自证 17/17); verify_script_health.py H1–H4 PASS
2026-10-12 11:06:01 +08:00
* 1. 对应的**环境变量**(生产由 systemd `EnvironmentFile=` / 启动器注入;本地由
* config.default.js 顶部的 `loadDeployEnv()` 注入 —— 注意 **R106-D9 起
* `JWT_SECRET` / `APP_KEYS` 属"非生产跳过键"**,本地开发不再继承生产值)
* 2. 仓库根 `scripts/.creds.local.json` 的对应键(本机开发用;`.gitignore` 已排除;
* `jwt_fallback_secret` 同时是 `scripts/_fixtures.py` 的末级来源,两侧同源 ⇒
security(config): 移除内置 JWT 密钥明文兜底,收敛到唯一解析入口(R105-D1) 问题(R104 卫生清理 H2 分类的唯二受版本控制命中): config.default.js 原为 const DEFAULT_JWT_SECRET = 'competition_jwt_secret_key_2026_br!cs'; secret: process.env.JWT_SECRET || DEFAULT_JWT_SECRET 该常量为**已入库、任何人可读**的明文,且与 scripts/.creds.local.json 的 jwt_fallback_secret 同值 ⇒ 等价于「JWT 签名密钥公开 ⇒ 任意人可伪造 token 冒充任意身份」。它与仓库既有纪律(_creds.py / _db.cjs / 本文件:459 对 DB_PASSWORD 的「缺失即阻断」)不一致 —— 同族问题各自实现必然漂移。 修法: · config/config.default.js - 删除 DEFAULT_JWT_SECRET 常量与 `||` 兜底,改调唯一解析入口; - 生产安全守卫中同步移除 `jwt.secret === DEFAULT_JWT_SECRET` 比对分支 (不再存在可比的兜底值;缺失时解析期即抛错)。 · 新增 scripts/_secret.cjs(与 _db.cjs / _creds.py 同一纪律) ① env JWT_SECRET → ② scripts/.creds.local.json:jwt_fallback_secret → ③ 明确失败(⛔ 刻意不提供明文兜底) 行为等价性:生产经 systemd EnvironmentFile、本地经 loadDeployEnv 从 deploy/.env.production 注入 JWT_SECRET,正常运行路径逐字不变;仅消除了 「密钥缺失时静默使用公开值」这一分支。 验证: · 后端真实重启 ⇒ 配置加载成功,生效 jwt.secret 指纹与注入值一致; · 端到端判别力(本地 7001 与生产 8023 各一次):新密钥签发 ⇒ 200, 旧密钥签发 ⇒ 401(auth.js jwt.verify 分支); · scripts/verify_no_secrets.py:HIGH 0,判别力自证 17/17; · scripts/verify_script_health.py:H1–H4 PASS。
2026-10-12 10:26:03 +08:00
* 自签 token 与后端验签必然一致)
security(config): 四个同族兜底缺陷一次收口 —— APP_KEYS / 弱口令 / IPv6 兜底 / 本地生产密钥同值(R106-D6~D9) 共同根因(R104 结论的延续):**同族问题各自实现必然漂移**。 本仓 DB_PASSWORD 与 JWT_SECRET(R105-D1)已统一为「缺凭据明确失败」, 但同族的另三处仍停在「可预测的公开兜底」上,另新增一处跨环境密钥同值。 D6 · APP_KEYS 派生兜底(原 config.default.js:157) 原:const DEFAULT_APP_KEYS = appInfo.name + '_competition_website_secret_key_2026'; config.keys = process.env.APP_KEYS || DEFAULT_APP_KEYS; 「派生」不等于安全:由 appInfo.name 可推算 ⇒ 等价于「Cookie 签名密钥公开 ⇒ 任何人可伪造已签名 Cookie」。收口到 _secret.cjs 唯一解析入口: · scripts/_secret.cjs 抽出通用 _loadSecret(),新增 loadAppKeys() (① env → ② scripts/.creds.local.json:app_keys → ③ 明确失败,⛔ 无兜底) · 生产安全守卫移除 config.keys === DEFAULT_APP_KEYS 比对分支(已无可比兜底值) · scripts/_creds.py 与 .creds.local.example.json 同步键面(两侧同源,永不漂移) D7 · 生产弱口令兜底(原 config.default.js:176) 原:config.testDefaultPassword = process.env.TEST_DEFAULT_PASSWORD || '123456'; 而 deploy/.env.production 的 TEST_DEFAULT_PASSWORD 一直是**注释状态** ⇒ 生产 实际取到的就是弱口令 '123456'。一旦走到「批量写默认口令」的路径(重置测试数据 / 显式 testFlag=true 的导入)会批量设弱口令 —— 属**静默弱口令**而非明确失败。 修法:生产(config.isProduction)未显式注入 ⇒ 置 null;使用点改走新增的 app/lib/passwordPolicy.requireTestPassword() 明确失败。测试环境语义逐字不变。 D8 · 本地 DB 兜底 host 引发 IPv6 建连超时(原 config.default.js:217) 原:host: process.env.DB_HOST || 'localhost'。Node >= 18 的 verbatim DNS 可能把 localhost 解析到 ::1,而本机 MariaDB 只听 IPv4 ⇒ **建连 ETIMEDOUT**(不报 1045、 不报 ECONNREFUSED),现象是「后端启动卡在 DB」,极易误判为「服务没起来」。 且本仓 scripts/_db.cjs:43 的兜底早已是 '127.0.0.1' —— 同子系统两套默认值必然漂移。 修法:兜底改为 IPv4 字面量(生产显式注入 DB_HOST ⇒ 逐字不变)。 配套(config.local.js 为 gitignored 本地文件,不入库):补 host/port 覆盖 —— 「第二道保险」原本只保账号/口令,反而漏掉了真正会出问题的连接目标。 D9 · 本地 dev 与生产共用同一 JWT/APP_KEYS(loadDeployEnv) 非生产跳过清单原本只含 DB_/REDIS_/ALIYUN_/SMS_,注释称「仍保留 JWT_SECRET / APP_KEYS …… 本地无副作用」—— 该结论不成立:本地进程持有 **生产签名密钥** ⇒ 本地 token 可冒充生产用户(反之亦然),两套环境的会话边界 实际被打通,属**安全属性**缺陷。 修法:新增 DEPLOY_ENV_LOCAL_SKIP_KEYS 精确跳过(⛔ 不用前缀以免误伤),本地改由 scripts/.creds.local.json 供值;Python 侧 scripts/_fixtures.py resolve_jwt_secret() 同步为「非生产优先本仓凭据文件」—— 否则脚本会用生产密钥签 token 交给本地后端 验签,必然 401(正是该函数注释里记录过的 23 项级联假红)。 行为等价性(生产逐字不变,已逐项实测): · D6/D8:生产显式注入 APP_KEYS / DB_HOST ⇒ 取值路径不变 · D7:生产 resetTestData 本有 403 守卫、导入默认 testFlag=false ⇒ 原不可达 · D9:isProdEnv=true ⇒ 不跳过任何键 ⇒ 生产加载路径不变 验证: · 配置层双环境断言:本地 jwt=8066e07d5683 / keys=e4cfe40ff3ea(≠ 生产 a05591750093 / ae0d155385c1);生产两值逐字不变;生产 testDefaultPassword=null · 本地后端真实重启(仅给 DB_PORT=3307、**不给 DB_HOST**)⇒ 启动成功, /api/health {"code":0,...,"db":"ok"};日志显示跳过 16 个键(含 JWT_SECRET/APP_KEYS) · D9 判决点 d9_verify_split.py:本地密钥签 token ⇒ 200;生产密钥 ⇒ **401** · 生产部署后 r106_prod_sentinel.js:pass=11 fail=0;/api/health 200 · 门禁:verify_no_secrets.py VERIFY_ALL_PASS(HIGH 0,判别力自证 17/17); verify_script_health.py H1–H4 PASS
2026-10-12 11:06:01 +08:00
* 3. **明确失败** —— ⛔ **刻意不提供明文兜底**:抛错并给出可行动的修复指引。
security(config): 移除内置 JWT 密钥明文兜底,收敛到唯一解析入口(R105-D1) 问题(R104 卫生清理 H2 分类的唯二受版本控制命中): config.default.js 原为 const DEFAULT_JWT_SECRET = 'competition_jwt_secret_key_2026_br!cs'; secret: process.env.JWT_SECRET || DEFAULT_JWT_SECRET 该常量为**已入库、任何人可读**的明文,且与 scripts/.creds.local.json 的 jwt_fallback_secret 同值 ⇒ 等价于「JWT 签名密钥公开 ⇒ 任意人可伪造 token 冒充任意身份」。它与仓库既有纪律(_creds.py / _db.cjs / 本文件:459 对 DB_PASSWORD 的「缺失即阻断」)不一致 —— 同族问题各自实现必然漂移。 修法: · config/config.default.js - 删除 DEFAULT_JWT_SECRET 常量与 `||` 兜底,改调唯一解析入口; - 生产安全守卫中同步移除 `jwt.secret === DEFAULT_JWT_SECRET` 比对分支 (不再存在可比的兜底值;缺失时解析期即抛错)。 · 新增 scripts/_secret.cjs(与 _db.cjs / _creds.py 同一纪律) ① env JWT_SECRET → ② scripts/.creds.local.json:jwt_fallback_secret → ③ 明确失败(⛔ 刻意不提供明文兜底) 行为等价性:生产经 systemd EnvironmentFile、本地经 loadDeployEnv 从 deploy/.env.production 注入 JWT_SECRET,正常运行路径逐字不变;仅消除了 「密钥缺失时静默使用公开值」这一分支。 验证: · 后端真实重启 ⇒ 配置加载成功,生效 jwt.secret 指纹与注入值一致; · 端到端判别力(本地 7001 与生产 8023 各一次):新密钥签发 ⇒ 200, 旧密钥签发 ⇒ 401(auth.js jwt.verify 分支); · scripts/verify_no_secrets.py:HIGH 0,判别力自证 17/17; · scripts/verify_script_health.py:H1–H4 PASS。
2026-10-12 10:26:03 +08:00
* 理由:密钥缺失属"注入链路断了"的真实故障,明确阻断远优于静默用公开值签名。
*
security(config): 四个同族兜底缺陷一次收口 —— APP_KEYS / 弱口令 / IPv6 兜底 / 本地生产密钥同值(R106-D6~D9) 共同根因(R104 结论的延续):**同族问题各自实现必然漂移**。 本仓 DB_PASSWORD 与 JWT_SECRET(R105-D1)已统一为「缺凭据明确失败」, 但同族的另三处仍停在「可预测的公开兜底」上,另新增一处跨环境密钥同值。 D6 · APP_KEYS 派生兜底(原 config.default.js:157) 原:const DEFAULT_APP_KEYS = appInfo.name + '_competition_website_secret_key_2026'; config.keys = process.env.APP_KEYS || DEFAULT_APP_KEYS; 「派生」不等于安全:由 appInfo.name 可推算 ⇒ 等价于「Cookie 签名密钥公开 ⇒ 任何人可伪造已签名 Cookie」。收口到 _secret.cjs 唯一解析入口: · scripts/_secret.cjs 抽出通用 _loadSecret(),新增 loadAppKeys() (① env → ② scripts/.creds.local.json:app_keys → ③ 明确失败,⛔ 无兜底) · 生产安全守卫移除 config.keys === DEFAULT_APP_KEYS 比对分支(已无可比兜底值) · scripts/_creds.py 与 .creds.local.example.json 同步键面(两侧同源,永不漂移) D7 · 生产弱口令兜底(原 config.default.js:176) 原:config.testDefaultPassword = process.env.TEST_DEFAULT_PASSWORD || '123456'; 而 deploy/.env.production 的 TEST_DEFAULT_PASSWORD 一直是**注释状态** ⇒ 生产 实际取到的就是弱口令 '123456'。一旦走到「批量写默认口令」的路径(重置测试数据 / 显式 testFlag=true 的导入)会批量设弱口令 —— 属**静默弱口令**而非明确失败。 修法:生产(config.isProduction)未显式注入 ⇒ 置 null;使用点改走新增的 app/lib/passwordPolicy.requireTestPassword() 明确失败。测试环境语义逐字不变。 D8 · 本地 DB 兜底 host 引发 IPv6 建连超时(原 config.default.js:217) 原:host: process.env.DB_HOST || 'localhost'。Node >= 18 的 verbatim DNS 可能把 localhost 解析到 ::1,而本机 MariaDB 只听 IPv4 ⇒ **建连 ETIMEDOUT**(不报 1045、 不报 ECONNREFUSED),现象是「后端启动卡在 DB」,极易误判为「服务没起来」。 且本仓 scripts/_db.cjs:43 的兜底早已是 '127.0.0.1' —— 同子系统两套默认值必然漂移。 修法:兜底改为 IPv4 字面量(生产显式注入 DB_HOST ⇒ 逐字不变)。 配套(config.local.js 为 gitignored 本地文件,不入库):补 host/port 覆盖 —— 「第二道保险」原本只保账号/口令,反而漏掉了真正会出问题的连接目标。 D9 · 本地 dev 与生产共用同一 JWT/APP_KEYS(loadDeployEnv) 非生产跳过清单原本只含 DB_/REDIS_/ALIYUN_/SMS_,注释称「仍保留 JWT_SECRET / APP_KEYS …… 本地无副作用」—— 该结论不成立:本地进程持有 **生产签名密钥** ⇒ 本地 token 可冒充生产用户(反之亦然),两套环境的会话边界 实际被打通,属**安全属性**缺陷。 修法:新增 DEPLOY_ENV_LOCAL_SKIP_KEYS 精确跳过(⛔ 不用前缀以免误伤),本地改由 scripts/.creds.local.json 供值;Python 侧 scripts/_fixtures.py resolve_jwt_secret() 同步为「非生产优先本仓凭据文件」—— 否则脚本会用生产密钥签 token 交给本地后端 验签,必然 401(正是该函数注释里记录过的 23 项级联假红)。 行为等价性(生产逐字不变,已逐项实测): · D6/D8:生产显式注入 APP_KEYS / DB_HOST ⇒ 取值路径不变 · D7:生产 resetTestData 本有 403 守卫、导入默认 testFlag=false ⇒ 原不可达 · D9:isProdEnv=true ⇒ 不跳过任何键 ⇒ 生产加载路径不变 验证: · 配置层双环境断言:本地 jwt=8066e07d5683 / keys=e4cfe40ff3ea(≠ 生产 a05591750093 / ae0d155385c1);生产两值逐字不变;生产 testDefaultPassword=null · 本地后端真实重启(仅给 DB_PORT=3307、**不给 DB_HOST**)⇒ 启动成功, /api/health {"code":0,...,"db":"ok"};日志显示跳过 16 个键(含 JWT_SECRET/APP_KEYS) · D9 判决点 d9_verify_split.py:本地密钥签 token ⇒ 200;生产密钥 ⇒ **401** · 生产部署后 r106_prod_sentinel.js:pass=11 fail=0;/api/health 200 · 门禁:verify_no_secrets.py VERIFY_ALL_PASS(HIGH 0,判别力自证 17/17); verify_script_health.py H1–H4 PASS
2026-10-12 11:06:01 +08:00
* ⚠️ 与 Python 侧 `scripts/_creds.py`(`JWT_FALLBACK_SECRET` / `APP_KEYS`)口径必须一致;
security(config): 移除内置 JWT 密钥明文兜底,收敛到唯一解析入口(R105-D1) 问题(R104 卫生清理 H2 分类的唯二受版本控制命中): config.default.js 原为 const DEFAULT_JWT_SECRET = 'competition_jwt_secret_key_2026_br!cs'; secret: process.env.JWT_SECRET || DEFAULT_JWT_SECRET 该常量为**已入库、任何人可读**的明文,且与 scripts/.creds.local.json 的 jwt_fallback_secret 同值 ⇒ 等价于「JWT 签名密钥公开 ⇒ 任意人可伪造 token 冒充任意身份」。它与仓库既有纪律(_creds.py / _db.cjs / 本文件:459 对 DB_PASSWORD 的「缺失即阻断」)不一致 —— 同族问题各自实现必然漂移。 修法: · config/config.default.js - 删除 DEFAULT_JWT_SECRET 常量与 `||` 兜底,改调唯一解析入口; - 生产安全守卫中同步移除 `jwt.secret === DEFAULT_JWT_SECRET` 比对分支 (不再存在可比的兜底值;缺失时解析期即抛错)。 · 新增 scripts/_secret.cjs(与 _db.cjs / _creds.py 同一纪律) ① env JWT_SECRET → ② scripts/.creds.local.json:jwt_fallback_secret → ③ 明确失败(⛔ 刻意不提供明文兜底) 行为等价性:生产经 systemd EnvironmentFile、本地经 loadDeployEnv 从 deploy/.env.production 注入 JWT_SECRET,正常运行路径逐字不变;仅消除了 「密钥缺失时静默使用公开值」这一分支。 验证: · 后端真实重启 ⇒ 配置加载成功,生效 jwt.secret 指纹与注入值一致; · 端到端判别力(本地 7001 与生产 8023 各一次):新密钥签发 ⇒ 200, 旧密钥签发 ⇒ 401(auth.js jwt.verify 分支); · scripts/verify_no_secrets.py:HIGH 0,判别力自证 17/17; · scripts/verify_script_health.py:H1–H4 PASS。
2026-10-12 10:26:03 +08:00
* 如需增删键,两边同时改。
*/
const fs = require('fs');
const path = require('path');
const LOCAL_FILE = path.join(__dirname, '..', '..', 'scripts', '.creds.local.json');
function _loadLocal() {
try {
return JSON.parse(fs.readFileSync(LOCAL_FILE, 'utf8'));
} catch (e) {
if (e.code === 'ENOENT') return {}; // 文件不存在合法(走环境变量)
// 损坏必须报错,不静默降级
throw new Error(`凭据文件解析失败 ${LOCAL_FILE}: ${e.message}`);
}
}
security(config): 四个同族兜底缺陷一次收口 —— APP_KEYS / 弱口令 / IPv6 兜底 / 本地生产密钥同值(R106-D6~D9) 共同根因(R104 结论的延续):**同族问题各自实现必然漂移**。 本仓 DB_PASSWORD 与 JWT_SECRET(R105-D1)已统一为「缺凭据明确失败」, 但同族的另三处仍停在「可预测的公开兜底」上,另新增一处跨环境密钥同值。 D6 · APP_KEYS 派生兜底(原 config.default.js:157) 原:const DEFAULT_APP_KEYS = appInfo.name + '_competition_website_secret_key_2026'; config.keys = process.env.APP_KEYS || DEFAULT_APP_KEYS; 「派生」不等于安全:由 appInfo.name 可推算 ⇒ 等价于「Cookie 签名密钥公开 ⇒ 任何人可伪造已签名 Cookie」。收口到 _secret.cjs 唯一解析入口: · scripts/_secret.cjs 抽出通用 _loadSecret(),新增 loadAppKeys() (① env → ② scripts/.creds.local.json:app_keys → ③ 明确失败,⛔ 无兜底) · 生产安全守卫移除 config.keys === DEFAULT_APP_KEYS 比对分支(已无可比兜底值) · scripts/_creds.py 与 .creds.local.example.json 同步键面(两侧同源,永不漂移) D7 · 生产弱口令兜底(原 config.default.js:176) 原:config.testDefaultPassword = process.env.TEST_DEFAULT_PASSWORD || '123456'; 而 deploy/.env.production 的 TEST_DEFAULT_PASSWORD 一直是**注释状态** ⇒ 生产 实际取到的就是弱口令 '123456'。一旦走到「批量写默认口令」的路径(重置测试数据 / 显式 testFlag=true 的导入)会批量设弱口令 —— 属**静默弱口令**而非明确失败。 修法:生产(config.isProduction)未显式注入 ⇒ 置 null;使用点改走新增的 app/lib/passwordPolicy.requireTestPassword() 明确失败。测试环境语义逐字不变。 D8 · 本地 DB 兜底 host 引发 IPv6 建连超时(原 config.default.js:217) 原:host: process.env.DB_HOST || 'localhost'。Node >= 18 的 verbatim DNS 可能把 localhost 解析到 ::1,而本机 MariaDB 只听 IPv4 ⇒ **建连 ETIMEDOUT**(不报 1045、 不报 ECONNREFUSED),现象是「后端启动卡在 DB」,极易误判为「服务没起来」。 且本仓 scripts/_db.cjs:43 的兜底早已是 '127.0.0.1' —— 同子系统两套默认值必然漂移。 修法:兜底改为 IPv4 字面量(生产显式注入 DB_HOST ⇒ 逐字不变)。 配套(config.local.js 为 gitignored 本地文件,不入库):补 host/port 覆盖 —— 「第二道保险」原本只保账号/口令,反而漏掉了真正会出问题的连接目标。 D9 · 本地 dev 与生产共用同一 JWT/APP_KEYS(loadDeployEnv) 非生产跳过清单原本只含 DB_/REDIS_/ALIYUN_/SMS_,注释称「仍保留 JWT_SECRET / APP_KEYS …… 本地无副作用」—— 该结论不成立:本地进程持有 **生产签名密钥** ⇒ 本地 token 可冒充生产用户(反之亦然),两套环境的会话边界 实际被打通,属**安全属性**缺陷。 修法:新增 DEPLOY_ENV_LOCAL_SKIP_KEYS 精确跳过(⛔ 不用前缀以免误伤),本地改由 scripts/.creds.local.json 供值;Python 侧 scripts/_fixtures.py resolve_jwt_secret() 同步为「非生产优先本仓凭据文件」—— 否则脚本会用生产密钥签 token 交给本地后端 验签,必然 401(正是该函数注释里记录过的 23 项级联假红)。 行为等价性(生产逐字不变,已逐项实测): · D6/D8:生产显式注入 APP_KEYS / DB_HOST ⇒ 取值路径不变 · D7:生产 resetTestData 本有 403 守卫、导入默认 testFlag=false ⇒ 原不可达 · D9:isProdEnv=true ⇒ 不跳过任何键 ⇒ 生产加载路径不变 验证: · 配置层双环境断言:本地 jwt=8066e07d5683 / keys=e4cfe40ff3ea(≠ 生产 a05591750093 / ae0d155385c1);生产两值逐字不变;生产 testDefaultPassword=null · 本地后端真实重启(仅给 DB_PORT=3307、**不给 DB_HOST**)⇒ 启动成功, /api/health {"code":0,...,"db":"ok"};日志显示跳过 16 个键(含 JWT_SECRET/APP_KEYS) · D9 判决点 d9_verify_split.py:本地密钥签 token ⇒ 200;生产密钥 ⇒ **401** · 生产部署后 r106_prod_sentinel.js:pass=11 fail=0;/api/health 200 · 门禁:verify_no_secrets.py VERIFY_ALL_PASS(HIGH 0,判别力自证 17/17); verify_script_health.py H1–H4 PASS
2026-10-12 11:06:01 +08:00
/**
* 通用解析:env → `.creds.local.json` → 明确失败。
*
* @param {object} o
* @param {string} o.envKey 环境变量名(如 'JWT_SECRET')
* @param {string} o.localKey `.creds.local.json` 中的键名(如 'jwt_fallback_secret')
* @param {string} o.purpose 失败时的用途说明(如 '签名/校验登录态')
* @param {string} o.builtin 被移除的兜底物名称(如 '内置密钥')
* @param {string} o.whyWrong 该兜底值为何不可接受的后果(如 'JWT 可被任意伪造')
* @returns {string} 生效中的密钥
*/
function _loadSecret(o) {
const { envKey, localKey, purpose, builtin, whyWrong } = o;
const fromEnv = process.env[envKey];
security(config): 移除内置 JWT 密钥明文兜底,收敛到唯一解析入口(R105-D1) 问题(R104 卫生清理 H2 分类的唯二受版本控制命中): config.default.js 原为 const DEFAULT_JWT_SECRET = 'competition_jwt_secret_key_2026_br!cs'; secret: process.env.JWT_SECRET || DEFAULT_JWT_SECRET 该常量为**已入库、任何人可读**的明文,且与 scripts/.creds.local.json 的 jwt_fallback_secret 同值 ⇒ 等价于「JWT 签名密钥公开 ⇒ 任意人可伪造 token 冒充任意身份」。它与仓库既有纪律(_creds.py / _db.cjs / 本文件:459 对 DB_PASSWORD 的「缺失即阻断」)不一致 —— 同族问题各自实现必然漂移。 修法: · config/config.default.js - 删除 DEFAULT_JWT_SECRET 常量与 `||` 兜底,改调唯一解析入口; - 生产安全守卫中同步移除 `jwt.secret === DEFAULT_JWT_SECRET` 比对分支 (不再存在可比的兜底值;缺失时解析期即抛错)。 · 新增 scripts/_secret.cjs(与 _db.cjs / _creds.py 同一纪律) ① env JWT_SECRET → ② scripts/.creds.local.json:jwt_fallback_secret → ③ 明确失败(⛔ 刻意不提供明文兜底) 行为等价性:生产经 systemd EnvironmentFile、本地经 loadDeployEnv 从 deploy/.env.production 注入 JWT_SECRET,正常运行路径逐字不变;仅消除了 「密钥缺失时静默使用公开值」这一分支。 验证: · 后端真实重启 ⇒ 配置加载成功,生效 jwt.secret 指纹与注入值一致; · 端到端判别力(本地 7001 与生产 8023 各一次):新密钥签发 ⇒ 200, 旧密钥签发 ⇒ 401(auth.js jwt.verify 分支); · scripts/verify_no_secrets.py:HIGH 0,判别力自证 17/17; · scripts/verify_script_health.py:H1–H4 PASS。
2026-10-12 10:26:03 +08:00
if (fromEnv) return fromEnv;
const local = _loadLocal();
security(config): 四个同族兜底缺陷一次收口 —— APP_KEYS / 弱口令 / IPv6 兜底 / 本地生产密钥同值(R106-D6~D9) 共同根因(R104 结论的延续):**同族问题各自实现必然漂移**。 本仓 DB_PASSWORD 与 JWT_SECRET(R105-D1)已统一为「缺凭据明确失败」, 但同族的另三处仍停在「可预测的公开兜底」上,另新增一处跨环境密钥同值。 D6 · APP_KEYS 派生兜底(原 config.default.js:157) 原:const DEFAULT_APP_KEYS = appInfo.name + '_competition_website_secret_key_2026'; config.keys = process.env.APP_KEYS || DEFAULT_APP_KEYS; 「派生」不等于安全:由 appInfo.name 可推算 ⇒ 等价于「Cookie 签名密钥公开 ⇒ 任何人可伪造已签名 Cookie」。收口到 _secret.cjs 唯一解析入口: · scripts/_secret.cjs 抽出通用 _loadSecret(),新增 loadAppKeys() (① env → ② scripts/.creds.local.json:app_keys → ③ 明确失败,⛔ 无兜底) · 生产安全守卫移除 config.keys === DEFAULT_APP_KEYS 比对分支(已无可比兜底值) · scripts/_creds.py 与 .creds.local.example.json 同步键面(两侧同源,永不漂移) D7 · 生产弱口令兜底(原 config.default.js:176) 原:config.testDefaultPassword = process.env.TEST_DEFAULT_PASSWORD || '123456'; 而 deploy/.env.production 的 TEST_DEFAULT_PASSWORD 一直是**注释状态** ⇒ 生产 实际取到的就是弱口令 '123456'。一旦走到「批量写默认口令」的路径(重置测试数据 / 显式 testFlag=true 的导入)会批量设弱口令 —— 属**静默弱口令**而非明确失败。 修法:生产(config.isProduction)未显式注入 ⇒ 置 null;使用点改走新增的 app/lib/passwordPolicy.requireTestPassword() 明确失败。测试环境语义逐字不变。 D8 · 本地 DB 兜底 host 引发 IPv6 建连超时(原 config.default.js:217) 原:host: process.env.DB_HOST || 'localhost'。Node >= 18 的 verbatim DNS 可能把 localhost 解析到 ::1,而本机 MariaDB 只听 IPv4 ⇒ **建连 ETIMEDOUT**(不报 1045、 不报 ECONNREFUSED),现象是「后端启动卡在 DB」,极易误判为「服务没起来」。 且本仓 scripts/_db.cjs:43 的兜底早已是 '127.0.0.1' —— 同子系统两套默认值必然漂移。 修法:兜底改为 IPv4 字面量(生产显式注入 DB_HOST ⇒ 逐字不变)。 配套(config.local.js 为 gitignored 本地文件,不入库):补 host/port 覆盖 —— 「第二道保险」原本只保账号/口令,反而漏掉了真正会出问题的连接目标。 D9 · 本地 dev 与生产共用同一 JWT/APP_KEYS(loadDeployEnv) 非生产跳过清单原本只含 DB_/REDIS_/ALIYUN_/SMS_,注释称「仍保留 JWT_SECRET / APP_KEYS …… 本地无副作用」—— 该结论不成立:本地进程持有 **生产签名密钥** ⇒ 本地 token 可冒充生产用户(反之亦然),两套环境的会话边界 实际被打通,属**安全属性**缺陷。 修法:新增 DEPLOY_ENV_LOCAL_SKIP_KEYS 精确跳过(⛔ 不用前缀以免误伤),本地改由 scripts/.creds.local.json 供值;Python 侧 scripts/_fixtures.py resolve_jwt_secret() 同步为「非生产优先本仓凭据文件」—— 否则脚本会用生产密钥签 token 交给本地后端 验签,必然 401(正是该函数注释里记录过的 23 项级联假红)。 行为等价性(生产逐字不变,已逐项实测): · D6/D8:生产显式注入 APP_KEYS / DB_HOST ⇒ 取值路径不变 · D7:生产 resetTestData 本有 403 守卫、导入默认 testFlag=false ⇒ 原不可达 · D9:isProdEnv=true ⇒ 不跳过任何键 ⇒ 生产加载路径不变 验证: · 配置层双环境断言:本地 jwt=8066e07d5683 / keys=e4cfe40ff3ea(≠ 生产 a05591750093 / ae0d155385c1);生产两值逐字不变;生产 testDefaultPassword=null · 本地后端真实重启(仅给 DB_PORT=3307、**不给 DB_HOST**)⇒ 启动成功, /api/health {"code":0,...,"db":"ok"};日志显示跳过 16 个键(含 JWT_SECRET/APP_KEYS) · D9 判决点 d9_verify_split.py:本地密钥签 token ⇒ 200;生产密钥 ⇒ **401** · 生产部署后 r106_prod_sentinel.js:pass=11 fail=0;/api/health 200 · 门禁:verify_no_secrets.py VERIFY_ALL_PASS(HIGH 0,判别力自证 17/17); verify_script_health.py H1–H4 PASS
2026-10-12 11:06:01 +08:00
const fromFile = local[localKey];
security(config): 移除内置 JWT 密钥明文兜底,收敛到唯一解析入口(R105-D1) 问题(R104 卫生清理 H2 分类的唯二受版本控制命中): config.default.js 原为 const DEFAULT_JWT_SECRET = 'competition_jwt_secret_key_2026_br!cs'; secret: process.env.JWT_SECRET || DEFAULT_JWT_SECRET 该常量为**已入库、任何人可读**的明文,且与 scripts/.creds.local.json 的 jwt_fallback_secret 同值 ⇒ 等价于「JWT 签名密钥公开 ⇒ 任意人可伪造 token 冒充任意身份」。它与仓库既有纪律(_creds.py / _db.cjs / 本文件:459 对 DB_PASSWORD 的「缺失即阻断」)不一致 —— 同族问题各自实现必然漂移。 修法: · config/config.default.js - 删除 DEFAULT_JWT_SECRET 常量与 `||` 兜底,改调唯一解析入口; - 生产安全守卫中同步移除 `jwt.secret === DEFAULT_JWT_SECRET` 比对分支 (不再存在可比的兜底值;缺失时解析期即抛错)。 · 新增 scripts/_secret.cjs(与 _db.cjs / _creds.py 同一纪律) ① env JWT_SECRET → ② scripts/.creds.local.json:jwt_fallback_secret → ③ 明确失败(⛔ 刻意不提供明文兜底) 行为等价性:生产经 systemd EnvironmentFile、本地经 loadDeployEnv 从 deploy/.env.production 注入 JWT_SECRET,正常运行路径逐字不变;仅消除了 「密钥缺失时静默使用公开值」这一分支。 验证: · 后端真实重启 ⇒ 配置加载成功,生效 jwt.secret 指纹与注入值一致; · 端到端判别力(本地 7001 与生产 8023 各一次):新密钥签发 ⇒ 200, 旧密钥签发 ⇒ 401(auth.js jwt.verify 分支); · scripts/verify_no_secrets.py:HIGH 0,判别力自证 17/17; · scripts/verify_script_health.py:H1–H4 PASS。
2026-10-12 10:26:03 +08:00
if (fromFile) return fromFile;
throw new Error(
security(config): 四个同族兜底缺陷一次收口 —— APP_KEYS / 弱口令 / IPv6 兜底 / 本地生产密钥同值(R106-D6~D9) 共同根因(R104 结论的延续):**同族问题各自实现必然漂移**。 本仓 DB_PASSWORD 与 JWT_SECRET(R105-D1)已统一为「缺凭据明确失败」, 但同族的另三处仍停在「可预测的公开兜底」上,另新增一处跨环境密钥同值。 D6 · APP_KEYS 派生兜底(原 config.default.js:157) 原:const DEFAULT_APP_KEYS = appInfo.name + '_competition_website_secret_key_2026'; config.keys = process.env.APP_KEYS || DEFAULT_APP_KEYS; 「派生」不等于安全:由 appInfo.name 可推算 ⇒ 等价于「Cookie 签名密钥公开 ⇒ 任何人可伪造已签名 Cookie」。收口到 _secret.cjs 唯一解析入口: · scripts/_secret.cjs 抽出通用 _loadSecret(),新增 loadAppKeys() (① env → ② scripts/.creds.local.json:app_keys → ③ 明确失败,⛔ 无兜底) · 生产安全守卫移除 config.keys === DEFAULT_APP_KEYS 比对分支(已无可比兜底值) · scripts/_creds.py 与 .creds.local.example.json 同步键面(两侧同源,永不漂移) D7 · 生产弱口令兜底(原 config.default.js:176) 原:config.testDefaultPassword = process.env.TEST_DEFAULT_PASSWORD || '123456'; 而 deploy/.env.production 的 TEST_DEFAULT_PASSWORD 一直是**注释状态** ⇒ 生产 实际取到的就是弱口令 '123456'。一旦走到「批量写默认口令」的路径(重置测试数据 / 显式 testFlag=true 的导入)会批量设弱口令 —— 属**静默弱口令**而非明确失败。 修法:生产(config.isProduction)未显式注入 ⇒ 置 null;使用点改走新增的 app/lib/passwordPolicy.requireTestPassword() 明确失败。测试环境语义逐字不变。 D8 · 本地 DB 兜底 host 引发 IPv6 建连超时(原 config.default.js:217) 原:host: process.env.DB_HOST || 'localhost'。Node >= 18 的 verbatim DNS 可能把 localhost 解析到 ::1,而本机 MariaDB 只听 IPv4 ⇒ **建连 ETIMEDOUT**(不报 1045、 不报 ECONNREFUSED),现象是「后端启动卡在 DB」,极易误判为「服务没起来」。 且本仓 scripts/_db.cjs:43 的兜底早已是 '127.0.0.1' —— 同子系统两套默认值必然漂移。 修法:兜底改为 IPv4 字面量(生产显式注入 DB_HOST ⇒ 逐字不变)。 配套(config.local.js 为 gitignored 本地文件,不入库):补 host/port 覆盖 —— 「第二道保险」原本只保账号/口令,反而漏掉了真正会出问题的连接目标。 D9 · 本地 dev 与生产共用同一 JWT/APP_KEYS(loadDeployEnv) 非生产跳过清单原本只含 DB_/REDIS_/ALIYUN_/SMS_,注释称「仍保留 JWT_SECRET / APP_KEYS …… 本地无副作用」—— 该结论不成立:本地进程持有 **生产签名密钥** ⇒ 本地 token 可冒充生产用户(反之亦然),两套环境的会话边界 实际被打通,属**安全属性**缺陷。 修法:新增 DEPLOY_ENV_LOCAL_SKIP_KEYS 精确跳过(⛔ 不用前缀以免误伤),本地改由 scripts/.creds.local.json 供值;Python 侧 scripts/_fixtures.py resolve_jwt_secret() 同步为「非生产优先本仓凭据文件」—— 否则脚本会用生产密钥签 token 交给本地后端 验签,必然 401(正是该函数注释里记录过的 23 项级联假红)。 行为等价性(生产逐字不变,已逐项实测): · D6/D8:生产显式注入 APP_KEYS / DB_HOST ⇒ 取值路径不变 · D7:生产 resetTestData 本有 403 守卫、导入默认 testFlag=false ⇒ 原不可达 · D9:isProdEnv=true ⇒ 不跳过任何键 ⇒ 生产加载路径不变 验证: · 配置层双环境断言:本地 jwt=8066e07d5683 / keys=e4cfe40ff3ea(≠ 生产 a05591750093 / ae0d155385c1);生产两值逐字不变;生产 testDefaultPassword=null · 本地后端真实重启(仅给 DB_PORT=3307、**不给 DB_HOST**)⇒ 启动成功, /api/health {"code":0,...,"db":"ok"};日志显示跳过 16 个键(含 JWT_SECRET/APP_KEYS) · D9 判决点 d9_verify_split.py:本地密钥签 token ⇒ 200;生产密钥 ⇒ **401** · 生产部署后 r106_prod_sentinel.js:pass=11 fail=0;/api/health 200 · 门禁:verify_no_secrets.py VERIFY_ALL_PASS(HIGH 0,判别力自证 17/17); verify_script_health.py H1–H4 PASS
2026-10-12 11:06:01 +08:00
`[配置缺失] 未取到 ${envKey} —— 无法安全地${purpose}。请任选其一:\n` +
` ① 设置环境变量 ${envKey}(生产/CI 推荐,deploy/.env.production 已含该键);\n` +
` ② 在 ${LOCAL_FILE} 写入键 "${localKey}"(本机开发用,该文件已被 .gitignore 排除);\n` +
`⛔ 本模块刻意不提供明文兜底:原 config.default.js 的${builtin}已移除,\n` +
` 因为「可预测的公开密钥」等价于「${whyWrong}」。`
security(config): 移除内置 JWT 密钥明文兜底,收敛到唯一解析入口(R105-D1) 问题(R104 卫生清理 H2 分类的唯二受版本控制命中): config.default.js 原为 const DEFAULT_JWT_SECRET = 'competition_jwt_secret_key_2026_br!cs'; secret: process.env.JWT_SECRET || DEFAULT_JWT_SECRET 该常量为**已入库、任何人可读**的明文,且与 scripts/.creds.local.json 的 jwt_fallback_secret 同值 ⇒ 等价于「JWT 签名密钥公开 ⇒ 任意人可伪造 token 冒充任意身份」。它与仓库既有纪律(_creds.py / _db.cjs / 本文件:459 对 DB_PASSWORD 的「缺失即阻断」)不一致 —— 同族问题各自实现必然漂移。 修法: · config/config.default.js - 删除 DEFAULT_JWT_SECRET 常量与 `||` 兜底,改调唯一解析入口; - 生产安全守卫中同步移除 `jwt.secret === DEFAULT_JWT_SECRET` 比对分支 (不再存在可比的兜底值;缺失时解析期即抛错)。 · 新增 scripts/_secret.cjs(与 _db.cjs / _creds.py 同一纪律) ① env JWT_SECRET → ② scripts/.creds.local.json:jwt_fallback_secret → ③ 明确失败(⛔ 刻意不提供明文兜底) 行为等价性:生产经 systemd EnvironmentFile、本地经 loadDeployEnv 从 deploy/.env.production 注入 JWT_SECRET,正常运行路径逐字不变;仅消除了 「密钥缺失时静默使用公开值」这一分支。 验证: · 后端真实重启 ⇒ 配置加载成功,生效 jwt.secret 指纹与注入值一致; · 端到端判别力(本地 7001 与生产 8023 各一次):新密钥签发 ⇒ 200, 旧密钥签发 ⇒ 401(auth.js jwt.verify 分支); · scripts/verify_no_secrets.py:HIGH 0,判别力自证 17/17; · scripts/verify_script_health.py:H1–H4 PASS。
2026-10-12 10:26:03 +08:00
);
}
security(config): 四个同族兜底缺陷一次收口 —— APP_KEYS / 弱口令 / IPv6 兜底 / 本地生产密钥同值(R106-D6~D9) 共同根因(R104 结论的延续):**同族问题各自实现必然漂移**。 本仓 DB_PASSWORD 与 JWT_SECRET(R105-D1)已统一为「缺凭据明确失败」, 但同族的另三处仍停在「可预测的公开兜底」上,另新增一处跨环境密钥同值。 D6 · APP_KEYS 派生兜底(原 config.default.js:157) 原:const DEFAULT_APP_KEYS = appInfo.name + '_competition_website_secret_key_2026'; config.keys = process.env.APP_KEYS || DEFAULT_APP_KEYS; 「派生」不等于安全:由 appInfo.name 可推算 ⇒ 等价于「Cookie 签名密钥公开 ⇒ 任何人可伪造已签名 Cookie」。收口到 _secret.cjs 唯一解析入口: · scripts/_secret.cjs 抽出通用 _loadSecret(),新增 loadAppKeys() (① env → ② scripts/.creds.local.json:app_keys → ③ 明确失败,⛔ 无兜底) · 生产安全守卫移除 config.keys === DEFAULT_APP_KEYS 比对分支(已无可比兜底值) · scripts/_creds.py 与 .creds.local.example.json 同步键面(两侧同源,永不漂移) D7 · 生产弱口令兜底(原 config.default.js:176) 原:config.testDefaultPassword = process.env.TEST_DEFAULT_PASSWORD || '123456'; 而 deploy/.env.production 的 TEST_DEFAULT_PASSWORD 一直是**注释状态** ⇒ 生产 实际取到的就是弱口令 '123456'。一旦走到「批量写默认口令」的路径(重置测试数据 / 显式 testFlag=true 的导入)会批量设弱口令 —— 属**静默弱口令**而非明确失败。 修法:生产(config.isProduction)未显式注入 ⇒ 置 null;使用点改走新增的 app/lib/passwordPolicy.requireTestPassword() 明确失败。测试环境语义逐字不变。 D8 · 本地 DB 兜底 host 引发 IPv6 建连超时(原 config.default.js:217) 原:host: process.env.DB_HOST || 'localhost'。Node >= 18 的 verbatim DNS 可能把 localhost 解析到 ::1,而本机 MariaDB 只听 IPv4 ⇒ **建连 ETIMEDOUT**(不报 1045、 不报 ECONNREFUSED),现象是「后端启动卡在 DB」,极易误判为「服务没起来」。 且本仓 scripts/_db.cjs:43 的兜底早已是 '127.0.0.1' —— 同子系统两套默认值必然漂移。 修法:兜底改为 IPv4 字面量(生产显式注入 DB_HOST ⇒ 逐字不变)。 配套(config.local.js 为 gitignored 本地文件,不入库):补 host/port 覆盖 —— 「第二道保险」原本只保账号/口令,反而漏掉了真正会出问题的连接目标。 D9 · 本地 dev 与生产共用同一 JWT/APP_KEYS(loadDeployEnv) 非生产跳过清单原本只含 DB_/REDIS_/ALIYUN_/SMS_,注释称「仍保留 JWT_SECRET / APP_KEYS …… 本地无副作用」—— 该结论不成立:本地进程持有 **生产签名密钥** ⇒ 本地 token 可冒充生产用户(反之亦然),两套环境的会话边界 实际被打通,属**安全属性**缺陷。 修法:新增 DEPLOY_ENV_LOCAL_SKIP_KEYS 精确跳过(⛔ 不用前缀以免误伤),本地改由 scripts/.creds.local.json 供值;Python 侧 scripts/_fixtures.py resolve_jwt_secret() 同步为「非生产优先本仓凭据文件」—— 否则脚本会用生产密钥签 token 交给本地后端 验签,必然 401(正是该函数注释里记录过的 23 项级联假红)。 行为等价性(生产逐字不变,已逐项实测): · D6/D8:生产显式注入 APP_KEYS / DB_HOST ⇒ 取值路径不变 · D7:生产 resetTestData 本有 403 守卫、导入默认 testFlag=false ⇒ 原不可达 · D9:isProdEnv=true ⇒ 不跳过任何键 ⇒ 生产加载路径不变 验证: · 配置层双环境断言:本地 jwt=8066e07d5683 / keys=e4cfe40ff3ea(≠ 生产 a05591750093 / ae0d155385c1);生产两值逐字不变;生产 testDefaultPassword=null · 本地后端真实重启(仅给 DB_PORT=3307、**不给 DB_HOST**)⇒ 启动成功, /api/health {"code":0,...,"db":"ok"};日志显示跳过 16 个键(含 JWT_SECRET/APP_KEYS) · D9 判决点 d9_verify_split.py:本地密钥签 token ⇒ 200;生产密钥 ⇒ **401** · 生产部署后 r106_prod_sentinel.js:pass=11 fail=0;/api/health 200 · 门禁:verify_no_secrets.py VERIFY_ALL_PASS(HIGH 0,判别力自证 17/17); verify_script_health.py H1–H4 PASS
2026-10-12 11:06:01 +08:00
/**
* JWT 签名密钥。
* @returns {string}
*/
function loadJwtSecret() {
return _loadSecret({
envKey: 'JWT_SECRET',
localKey: 'jwt_fallback_secret',
purpose: '签名/校验登录态',
builtin: '内置密钥',
whyWrong: 'JWT 可被任意伪造',
});
}
/**
* Cookie 签名密钥(Egg `config.keys`)。
* @returns {string}
*/
function loadAppKeys() {
return _loadSecret({
envKey: 'APP_KEYS',
localKey: 'app_keys',
purpose: '签发/校验 Cookie 签名',
builtin: '内置兜底值',
whyWrong: 'Cookie 可被任意伪造',
});
}
module.exports = { loadJwtSecret, loadAppKeys };