Files
competition-website/add_word_cols.js
qubiaobiao a019df510d chore(security): 处置已入库的本地 DB 口令(P1-P3;P4 改历史不做)
背景(R14 登记的 §16.3):
  git grep -l "74191221" HEAD 曾命中 7 个文件,全为本地 MariaDB root 口令
  (已核实无生产凭据)。本次按登记项执行 P1-P3;P4(git filter-repo 改写
  历史)明确不做。

P1  database/config.json(dev/test 两段明文口令)
  - 加入 .gitignore
  - 新增 database/config.json.example(口令为 CHANGE_ME 占位符)
  - git rm --cached(保留磁盘文件,本地 sequelize-cli 不受影响)
  - 注意:这是对共享仓库的文件移除。push 后其他克隆需
    cp database/config.json.example database/config.json 并填入口令。

P2  五个脚本口令环境变量化(去明文兜底)
  - add_word_cols.js / add_wx_link.js / show_tables.js / seed-carousel.js
    内联 '74191221' 改为 process.env.DB_PASSWORD(另支持 DB_HOST/DB_USER/DB_NAME)
  - scripts/diagnose_upload_download.cjs:process.env.DB_PASS || '74191221'
    改为 process.env.DB_PASS;用法注释中的示例口令改为占位符
  - 有意的行为变更:未注入 DB_PASSWORD 时明确失败,不再静默使用公开口令

P3  config/config.default.js 消除「生产静默回退公开口令」
  - sequelize.password 去掉 || '74191221'
  - redis.client.password 去掉 || '74191221'
  - 生产安全守卫新增 DB_PASSWORD 缺失阻断(与 JWT_SECRET/APP_KEYS 同源同形)
  - deploy/.env.production 本就含 DB_PASSWORD ⇒ 现网零影响

验证(全部实测):
  - git grep 74191221 工作树零命中(HEAD 仍有,属 P4 不做范围)
  - 裸启动(无任何 DB 变量)成功:本地口令由 config.local.js 提供
  - 端到端:health db:ok + admin-login code=0
  - MySQL 侧独立取证:processlist = root @ competition_db
  - 变异测试(改坏 config.local.js 口令)⇒ 报 1045 且用户名仍为 root
    ⇒ 同证 P3 兜底确已移除 + R18 的生产账号过滤仍生效
  - P2 双向验证:无 DB_PASSWORD 失败 / 注入后正常列 27 张表
  - 门禁四连:verify_scoring_e2e 17/0、verify_exam_submit_integrity 46/0、
    verify_big_question --cleanup(清理+窗口还原)、check_no_proxy_guard 5 变异全检出
2026-09-23 16:34:08 +08:00

28 lines
1.2 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
const mysql = require('mysql2/promise');
(async () => {
// 🔴 口令不再内置明文(2026-09-23 入库口令处置 P2)。运行前请注入 DB_PASSWORD:
// DB_PASSWORD='<本机 DB 口令>' node add_word_cols.js
const c = await mysql.createConnection({
host: process.env.DB_HOST || 'localhost',
user: process.env.DB_USER || 'root',
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME || 'competition_db',
});
try {
await c.query("ALTER TABLE exam_submissions ADD COLUMN word_file_path VARCHAR(500) NULL DEFAULT NULL COMMENT 'Word答案文件路径' AFTER answers");
console.log('Added word_file_path');
} catch (e) {
if (e.code === 'ER_DUP_FIELDNAME') console.log('word_file_path exists');
else throw e;
}
try {
await c.query("ALTER TABLE exam_submissions ADD COLUMN word_file_name VARCHAR(255) NULL DEFAULT NULL COMMENT 'Word答案文件名' AFTER word_file_path");
console.log('Added word_file_name');
} catch (e) {
if (e.code === 'ER_DUP_FIELDNAME') console.log('word_file_name exists');
else throw e;
}
const [cols] = await c.query('SHOW COLUMNS FROM exam_submissions');
cols.forEach(x => console.log(x.Field, x.Type, x.Null));
await c.end();
})();