fix: HIGH+MEDIUM bugs from full audit - listen error, JWT, upload, mailer, webhook

This commit is contained in:
2026-07-13 03:49:02 +08:00
parent 7c03fe7635
commit 532efb962f
8 changed files with 56 additions and 29 deletions

View File

@@ -4,8 +4,13 @@ const { getConfig } = require('../db');
function getSecret() {
try {
const cfg = getConfig();
return cfg?.jwt_secret || process.env.JWT_SECRET || 'mc-dev-secret';
} catch { return process.env.JWT_SECRET || 'mc-dev-secret'; }
const secret = cfg?.jwt_secret || process.env.JWT_SECRET;
if (secret) return secret;
throw new Error('JWT_SECRET not configured');
} catch (e) {
if (e.message === 'JWT_SECRET not configured') throw e;
return process.env.JWT_SECRET || (() => { throw new Error('JWT_SECRET not configured'); })();
}
}
function generateToken(user) {

View File

@@ -33,13 +33,14 @@ function sanitize(value) {
}
function sanitizeBody(req, res, next) {
if (req.body) {
for (const key of Object.keys(req.body)) {
if (typeof req.body[key] === 'string') {
req.body[key] = sanitize(req.body[key]);
}
function walk(obj) {
if (!obj || typeof obj !== 'object') return;
for (const key of Object.keys(obj)) {
if (typeof obj[key] === 'string') obj[key] = sanitize(obj[key]);
else if (typeof obj[key] === 'object') walk(obj[key]);
}
}
walk(req.body);
next();
}

View File

@@ -24,7 +24,7 @@ function validateMagicBytes(buffer, mime) {
'image/png': () => head[0] === 0x89 && head[1] === 0x50 && head[2] === 0x4E && head[3] === 0x47,
'image/gif': () => head.toString('ascii', 0, 6) === 'GIF89a' || head.toString('ascii', 0, 6) === 'GIF87a',
'image/webp': () => head.toString('ascii', 0, 4) === 'RIFF' && head.toString('ascii', 8, 12) === 'WEBP',
'video/mp4': () => buffer.includes(Buffer.from('ftyp')),
'video/mp4': () => buf[4] === 0x66 && buf[5] === 0x74 && buf[6] === 0x79 && buf[7] === 0x70,
'video/webm': () => head[0] === 0x1A && head[1] === 0x45 && head[2] === 0xDF && head[3] === 0xA3,
};
if (!sigs[mime]) return false;
@@ -58,18 +58,22 @@ const upload = multer({
function finalizeUpload(req, res, next) {
if (!req.files || req.files.length === 0) return next();
for (const file of req.files) {
const buf = fs.readFileSync(file.path);
if (!ALLOWED_MIME[file.mimetype]) {
fs.unlinkSync(file.path);
return res.status(400).json({ error: `不支持的文件类型: ${file.mimetype}` });
}
if (!validateMagicBytes(buf, file.mimetype)) {
fs.unlinkSync(file.path);
return res.status(400).json({ error: '文件内容与声明类型不符,可能是恶意文件' });
const filePaths = [];
try {
for (const file of req.files) {
const fd = fs.openSync(file.path, 'r');
const buf = Buffer.alloc(256);
fs.readSync(fd, buf, 0, 256, 0);
fs.closeSync(fd);
filePaths.push(file.path);
if (!ALLOWED_MIME[file.mimetype]) throw new Error(`不支持的文件类型: ${file.mimetype}`);
if (!validateMagicBytes(buf, file.mimetype)) throw new Error('文件内容与声明类型不符,可能是恶意文件');
}
next();
} catch (e) {
for (const fp of filePaths) { try { fs.unlinkSync(fp); } catch {} }
return res.status(400).json({ error: e.message || '文件校验失败' });
}
next();
}
module.exports = { upload, finalizeUpload, validateMagicBytes, UPLOAD_DIR, ALLOWED_MIME };