fix(security): 认证/越权/注入/上传/凭据全链路加固

由 OCR(open-codereview.ai,deepseek-v4-flash)审查驱动修复:
- JWT 生产必填、密码 8-72 字节、防枚举;全局 ValidationPipe
- classes/dashboard/schedules/students/attendance/archive/exams 越权与 IDOR 修复
- LIKE 通配符转义(14 处);上传 10MB 上限 + MIME 白名单 + 附件 XSS
- 集成配置 appSecret AES 加密 + 回填脚本;审计 best-effort;IP 来源防伪造

Reviewed-by: OCR (open-codereview.ai)
This commit is contained in:
2026-08-09 21:29:22 +08:00
parent 3bcad138a1
commit 99ea931409
61 changed files with 1249 additions and 175 deletions

View File

@@ -0,0 +1,67 @@
/// <reference types="node" />
import datasource from '../datasource';
import { encryptSecret, isEncryptedSecret } from '../src/integration/config/secret-crypto';
import { IntegrationConfigDetail } from '../src/integration/entities/integration-config.entity';
/** integration_config_detail.content JSON 中与本次回填相关的结构。 */
interface StoredConfigContent {
type?: unknown;
verify?: unknown;
config?: Record<string, unknown>;
}
async function main(): Promise<void> {
// 主动检查加密密钥:缺失时 getEncryptionKey() 会静默使用开发回退密钥,
// 绝不能用回退密钥加密生产数据,因此未配置时直接报错退出。
if (!process.env.AI_CONFIG_ENCRYPTION_KEY) {
throw new Error(
'AI_CONFIG_ENCRYPTION_KEY 未设置:为避免使用开发回退密钥加密数据,请先配置 AI_CONFIG_ENCRYPTION_KEY 再运行回填脚本',
);
}
await datasource.initialize();
console.log('已连接数据库,开始回填第三方集成配置 appSecret 加密...');
try {
const repo = datasource.getRepository(IntegrationConfigDetail);
const rows = await repo.find();
let processed = 0;
for (const row of rows) {
if (!row.content) continue;
let parsed: StoredConfigContent;
try {
parsed = JSON.parse(row.content) as StoredConfigContent;
} catch {
continue;
}
const config = parsed.config;
if (!config || typeof config !== 'object' || Array.isArray(config)) continue;
const appSecret = config.appSecret;
if (typeof appSecret !== 'string' || !appSecret || isEncryptedSecret(appSecret)) continue;
config.appSecret = encryptSecret(appSecret);
row.content = JSON.stringify(parsed);
await repo.save(row);
processed += 1;
}
console.log(`回填完成:共处理 ${processed} 行(加密 appSecret`);
} finally {
// 无论成功还是失败都关闭连接池,避免泄漏
try {
await datasource.destroy();
} catch {
// 销毁失败不影响主流程结果
}
}
}
main().catch((error) => {
console.error('回填失败:', error);
process.exitCode = 1;
});