|
|
14a110eb5f
|
docs(auth): R113-㉔ 哈希来源注释同步(schema.sql 已统一为 bcrypt)
㉔ 采纳 bcrypt:database/schema.sql 的初始口令由 SHA256(admin) 统一为 bcrypt(admin123) ⇒ 与 seeder 002 同值同形态(两条建库路径收敛)。本文件同步两处来源注释(⛔ 未删原文,保留可追溯性):
① isDefaultAdminHash 头注 —— 哈希通道来源改为「R113 前 schema.sql 无盐 SHA256;R113 起 schema.sql 与 seeder 002 统一为同一固定盐 bcrypt」;
② KNOWN_DEFAULT_ADMIN_HASHES 来源注释 —— SHA256 项改注为「历史值保留」(旧库/旧备份仍须拦截,且非默认口令不会命中)、bcrypt 项注明「两处已收敛为同值」。
验证:node --check ✔;单测 34/34;变异 3/3;R112 驱动回归 45/45;R113 判别力自证 9/9 + 5 变异全红(仓外 .workbuddy/tmp/r113/verify_r113_changes.cjs);verify_no_secrets HIGH 0/CAND 2(零新增);verify_tautology_guard PASS;verify_constant_shape_guard PASS;三文件 CR=0。
|
2026-10-14 11:18:08 +08:00 |
|
|
|
4f78995f1c
|
fix(auth): 默认口令守卫改为哈希值判据(双通道 + 下沉单源)
R112-㉒:原守卫是「口令字面」判据(DEFAULT_ADMIN_PASSWORDS.includes),任何未枚举的默认口令均绕过。改为双通道并下沉单源:① 哈希通道 isDefaultAdminHash(admin.password) —— 与输入无关,字面比对已发布默认哈希(SHA256('admin') + 固定盐 bcrypt)并以 bcrypt.compareSync 遍历候选口令,覆盖「同口令、不同盐」的重新播种;② 口令通道沿用原逻辑。清单与哈希集下沉至 lib/passwordPolicy.js 成为唯一真相源(controller 删本地声明)。bcrypt 延迟加载 + try/catch ⇒ 无 bcrypt 时降级为仅字面比对且不抛异常。
验证:node --check x2;单测 34/34;变异 3/3 红;仓外判别力自证 45/45 + 4 变异全红;verify_no_secrets HIGH 0/CAND 2(零新增);verify_tautology_guard PASS;verify_constant_shape_guard PASS;6 文件 CR=0。连带失真同步:守卫行 387->394 ⇒ schema.sql:218 + docs/modules.md:581。
|
2026-10-14 11:06:23 +08:00 |
|
|
|
76ac1c7b6b
|
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 |
|
|
|
a2e24aba2a
|
feat(auth): 口令强度策略(弱口令黑名单 / 账号派生值 / 长度上限)
背景(R61 实测):浏览器密码管理器在「设置密码」页预填 123456,考生若直接提交,该口令即成为其账号真实口令;而后端此前仅由 validate 校验「长度 >= 6」,弱口令完全合法。R62 已从前端修掉预填,本次补齐后端这道闸门。
- app/lib/passwordPolicy.js + password_blacklist.json:口令策略唯一事实来源。黑名单刻意做成纯 JSON,供 Node 运行时与运维脚本共用,避免两份清单漂移。
- controller/auth.js:activate() 与 resetPassword() 接入策略(错误码 2001 / 7001),位置在 validate 之后、短信校验之前(纯本地计算、无副作用、快速失败)。
- validate/index.js:补 max:20,与前端 el-form 规则对齐(此前只有前端限制长度上限)。
- scripts/_db.cjs:DB 配置解析唯一入口;scan_weak_password_accounts.cjs:存量弱口令只读扫描器;verify_password_policy_api.cjs:真接口验证(含「强口令通过」正向对照臂,防假绿)。
|
2026-09-30 11:56:35 +08:00 |
|