diff --git a/.gitignore b/.gitignore index c59e420..1601502 100644 --- a/.gitignore +++ b/.gitignore @@ -19,7 +19,22 @@ Thumbs.db # 环境配置 .env +.env.local +.env.*.local !backend/.env.example +config.json +config.*.json +!**/config.example.json + +# 敏感配置文件 +*.key +*.pem +*.crt +*.cer +*.p12 +*.pfx +secrets.json +credentials.json # SSL证书 certbot/ diff --git a/backend/server.js b/backend/server.js index 73b2a8d..66da5e8 100644 --- a/backend/server.js +++ b/backend/server.js @@ -21,24 +21,31 @@ const app = express(); const PORT = process.env.PORT || 40001; -// 配置CORS -const allowedOrigins = process.env.ALLOWED_ORIGINS +// 配置CORS - 严格白名单模式 +const allowedOrigins = process.env.ALLOWED_ORIGINS ? process.env.ALLOWED_ORIGINS.split(',').map(origin => origin.trim()) - : ['*']; + : []; // 默认为空数组,不允许任何域名 const corsOptions = { credentials: true, origin: (origin, callback) => { - // 允许所有来源(仅限开发环境) - if (allowedOrigins.includes('*')) { - if (process.env.NODE_ENV === 'production') { - console.warn('⚠️ 警告: 生产环境建议配置具体的ALLOWED_ORIGINS,而不是使用 *'); - } - callback(null, true); + // 生产环境必须配置白名单 + if (allowedOrigins.length === 0 && process.env.NODE_ENV === 'production') { + console.error('❌ 错误: 生产环境必须配置 ALLOWED_ORIGINS 环境变量!'); + callback(new Error('CORS未配置')); return; } - // 允许来自配置列表中的域名 + // 开发环境如果没有配置,允许 localhost + if (allowedOrigins.length === 0) { + const devOrigins = ['http://localhost:3000', 'http://127.0.0.1:3000']; + if (!origin || devOrigins.some(o => origin.startsWith(o))) { + callback(null, true); + return; + } + } + + // 允许来自白名单中的域名 if (!origin || allowedOrigins.includes(origin)) { callback(null, true); } else { @@ -53,6 +60,63 @@ app.use(cors(corsOptions)); app.use(express.json()); app.use(cookieParser()); +// 安全响应头中间件 +app.use((req, res, next) => { + // 防止点击劫持 + res.setHeader('X-Frame-Options', 'SAMEORIGIN'); + // 防止MIME类型嗅探 + res.setHeader('X-Content-Type-Options', 'nosniff'); + // XSS保护 + res.setHeader('X-XSS-Protection', '1; mode=block'); + // HTTPS严格传输安全 + if (req.secure || req.headers['x-forwarded-proto'] === 'https') { + res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains'); + } + // 内容安全策略 + res.setHeader('Content-Security-Policy', "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline';"); + // 隐藏X-Powered-By + res.removeHeader('X-Powered-By'); + next(); +}); + +// XSS过滤中间件(用于用户输入) +function sanitizeInput(str) { + if (typeof str !== 'string') return str; + return str + .replace(/[<>'"]/g, (char) => { + const map = { + '<': '<', + '>': '>', + '"': '"', + "'": ''' + }; + return map[char]; + }); +} + +// 应用XSS过滤到所有POST/PUT请求的body +app.use((req, res, next) => { + if ((req.method === 'POST' || req.method === 'PUT') && req.body) { + // 递归过滤所有字符串字段 + function sanitizeObject(obj) { + if (typeof obj === 'string') { + return sanitizeInput(obj); + } else if (Array.isArray(obj)) { + return obj.map(item => sanitizeObject(item)); + } else if (obj && typeof obj === 'object') { + const sanitized = {}; + for (const [key, value] of Object.entries(obj)) { + sanitized[key] = sanitizeObject(value); + } + return sanitized; + } + return obj; + } + req.body = sanitizeObject(req.body); + } + next(); +}); + // 请求日志 app.use((req, res, next) => { console.log(`[${new Date().toISOString()}] ${req.method} ${req.path}`); diff --git a/deploy.sh b/deploy.sh index 21766d3..8fe2bea 100644 --- a/deploy.sh +++ b/deploy.sh @@ -65,9 +65,16 @@ NODE_ENV=production ADMIN_USERNAME=admin ADMIN_PASSWORD=admin123 STORAGE_ROOT=/app/storage -ALLOWED_ORIGINS=* +# CORS配置 - 生产环境必须设置! +# 示例: ALLOWED_ORIGINS=https://yourdomain.com +ALLOWED_ORIGINS= COOKIE_SECURE=false ENVEOF + echo "" + echo "⚠️ 警告: ALLOWED_ORIGINS未配置!" + echo " 生产环境必须在backend/.env中设置ALLOWED_ORIGINS" + echo " 示例: ALLOWED_ORIGINS=https://cs.workyai.cn" + echo "" fi # 生成随机JWT密钥 diff --git a/nginx/nginx.conf b/nginx/nginx.conf index 11c939d..9208223 100644 --- a/nginx/nginx.conf +++ b/nginx/nginx.conf @@ -5,6 +5,26 @@ server { # 设置最大上传文件大小为10GB client_max_body_size 10G; + # 安全响应头 + add_header X-Frame-Options "SAMEORIGIN" always; + add_header X-Content-Type-Options "nosniff" always; + add_header X-XSS-Protection "1; mode=block" always; + add_header Referrer-Policy "no-referrer-when-downgrade" always; + + # 隐藏Nginx版本 + server_tokens off; + + # 禁止访问隐藏文件和敏感文件 + location ~ /\. { + deny all; + return 404; + } + + location ~ \.(env|git|config|key|pem|crt)$ { + deny all; + return 404; + } + # 前端静态文件 location / { root /usr/share/nginx/html;