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。
This commit is contained in:
2026-10-14 11:06:23 +08:00
parent ae436c01ca
commit 4f78995f1c
2 changed files with 101 additions and 2 deletions

View File

@@ -384,8 +384,16 @@ class AuthController extends Controller {
// 详见 `.workbuddy/memory/BACKLOG.md` 候选⑱/㉑。
// ⚠️ 结构性缺口**未消除**:本守卫是「口令字面」判据 ⇒ 任何未枚举的默认口令均绕过;
// 彻底治法应改为「哈希值判据」(DB 值 ∈ 已知默认哈希集)。见候选㉑。
const DEFAULT_ADMIN_PASSWORDS = ['admin', 'admin123', 'admin123456'];
const usingDefaultPassword = DEFAULT_ADMIN_PASSWORDS.includes(password);
// ✅ 【R112 已消除 · 候选㉒】上述「口令字面」单通道已升级为**双通道**,并把清单下沉为**单源**:
// ① 口令通道:`DEFAULT_ADMIN_PASSWORDS.includes(password)`(原逻辑原样保留);
// ② 哈希通道:`isDefaultAdminHash(admin.password)` —— **与用户输入无关**,直接判 DB 存储值
// 是否为已发布的默认哈希(无盐 SHA256 + 固定盐 bcrypt),并对候选口令做 bcrypt 遍历,
// 以覆盖「同口令、**不同盐**」的重新播种场景(见 `002-seed-admin.js` 的 ADMIN_INIT_PASSWORD 分支)。
// ⇒ 上面那条「未枚举即绕过」的绕过面已消除;仅剩「新增内置凭据未登记」这一**显式维护点**
// (清单已下沉至 `lib/passwordPolicy.js` 模块头 ⚠️,⛔ 勿在本控制器内再写第二份)。
const usingDefaultPassword =
passwordPolicy.isDefaultAdminHash(admin.password) ||
passwordPolicy.DEFAULT_ADMIN_PASSWORDS.includes(password);
const isProd = this.app.config.isProduction;
if (usingDefaultPassword && isProd) {
ctx.logger.error(`[安全阻断] 管理员 ${admin.username} 在生产环境尝试使用默认密码登录,已拒绝`);