From 0aed11cb0174ac64ec0adfac4cbb15fbb9296c2e Mon Sep 17 00:00:00 2001 From: xcbyy <2578898740@qq.com> Date: Mon, 19 Oct 2026 09:31:44 +0800 Subject: [PATCH] =?UTF-8?q?security:=20CORS=20=E6=94=B6=E6=95=9B=E4=B8=BA?= =?UTF-8?q?=E7=99=BD=E5=90=8D=E5=8D=95=20+=20=E5=9F=BA=E7=A1=80=E5=AE=89?= =?UTF-8?q?=E5=85=A8=E5=93=8D=E5=BA=94=E5=A4=B4=20+=20=E9=9A=90=E8=97=8F?= =?UTF-8?q?=E6=8A=80=E6=9C=AF=E6=A0=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - CORS 默认仅同源可用,跨域需显式配置 CORS_ORIGINS(非生产默认放行 localhost:5173) - 新增安全响应头:X-Content-Type-Options / X-Frame-Options / Referrer-Policy / X-XSS-Protection / Permissions-Policy;生产附加 HSTS - 关闭 X-Powered-By,设置 trust proxy - 文档补充 CORS_ORIGINS;新增回归护栏 cors-security-headers.spec.ts(85 套件/627 用例全绿) --- docs/部署验收与1分钱支付闭环.md | 3 ++ server/src/index.ts | 41 +++++++++++++++++++++- server/tests/cors-security-headers.spec.ts | 25 +++++++++++++ 3 files changed, 68 insertions(+), 1 deletion(-) create mode 100644 server/tests/cors-security-headers.spec.ts diff --git a/docs/部署验收与1分钱支付闭环.md b/docs/部署验收与1分钱支付闭环.md index 4772613..68dbf12 100644 --- a/docs/部署验收与1分钱支付闭环.md +++ b/docs/部署验收与1分钱支付闭环.md @@ -25,6 +25,9 @@ MYSQL_DATABASE=peiwan # ORDER_TIMEOUT_SCAN_MS=60000 # DISABLE_ORDER_TIMEOUT_SCAN=0 +# 可选:跨域白名单(逗号分隔;默认仅同源可用,小程序/服务端调用不受影响) +# CORS_ORIGINS=https://admin.example.com,https://h5.example.com + # 可选:微信(若已在后台"微信支付配置"页填写,则无需在这里重复;后台配置优先) # WX_APP_ID=... # WX_APP_SECRET=... diff --git a/server/src/index.ts b/server/src/index.ts index f3e060f..5dd7135 100644 --- a/server/src/index.ts +++ b/server/src/index.ts @@ -34,7 +34,46 @@ import { setWeChatRefundFn } from './core/pay/refund-bridge'; const app = express(); const port = process.env.PORT || 3000; -app.use(cors()); +// 反向代理(Nginx)后正确识别客户端 IP(登录限流等使用) +app.set('trust proxy', 1); +// 隐藏技术栈标识 +app.disable('x-powered-by'); + +// CORS 收敛:默认仅同源可用;如需跨域,配置 CORS_ORIGINS(逗号分隔)显式放行 +const configuredCorsOrigins = String(process.env.CORS_ORIGINS || '') + .split(',') + .map((s) => s.trim()) + .filter(Boolean); +const allowedCorsOrigins = new Set(configuredCorsOrigins); +if (process.env.NODE_ENV !== 'production') { + // 本地开发默认允许 Vite dev server 直连 API(若未使用代理) + allowedCorsOrigins.add('http://localhost:5173'); + allowedCorsOrigins.add('http://127.0.0.1:5173'); + allowedCorsOrigins.add('http://localhost:3000'); +} +app.use( + cors({ + origin(origin, cb) { + // 非浏览器请求(无 Origin,如小程序/服务端)放行 + if (!origin) return cb(null, true); + return cb(null, allowedCorsOrigins.has(origin)); + }, + credentials: false, + }) +); + +// 基础安全响应头 +app.use((_req: Request, res: Response, next: NextFunction) => { + res.setHeader('X-Content-Type-Options', 'nosniff'); + res.setHeader('X-Frame-Options', 'DENY'); + res.setHeader('Referrer-Policy', 'no-referrer'); + res.setHeader('X-XSS-Protection', '1; mode=block'); + res.setHeader('Permissions-Policy', 'geolocation=(), microphone=(), camera=()'); + if (process.env.NODE_ENV === 'production') { + res.setHeader('Strict-Transport-Security', 'max-age=15552000; includeSubDomains'); + } + next(); +}); app.use( express.json({ // 保留原始 HTTP 报文,供微信支付 V3 回调验签使用 diff --git a/server/tests/cors-security-headers.spec.ts b/server/tests/cors-security-headers.spec.ts new file mode 100644 index 0000000..58a9217 --- /dev/null +++ b/server/tests/cors-security-headers.spec.ts @@ -0,0 +1,25 @@ +// 已读取AGENTS.md +import * as fs from 'fs'; +import * as path from 'path'; + +/** + * 第六十九批回归闸门:CORS 收敛 + 基础安全响应头 + */ +describe('第六十九批 CORS 与安全响应头闸门', () => { + const rootDir = path.resolve(__dirname, '../../'); + const indexCode = fs.readFileSync(path.join(rootDir, 'server/src/index.ts'), 'utf-8'); + + it('CORS 不再全开,改为白名单(CORS_ORIGINS)', () => { + expect(indexCode).not.toContain('app.use(cors());'); + expect(indexCode).toContain('CORS_ORIGINS'); + expect(indexCode).toContain('allowedCorsOrigins.has(origin)'); + }); + + it('设置基础安全响应头并隐藏技术栈', () => { + expect(indexCode).toContain("app.disable('x-powered-by')"); + expect(indexCode).toContain("'X-Content-Type-Options', 'nosniff'"); + expect(indexCode).toContain("'X-Frame-Options', 'DENY'"); + expect(indexCode).toContain("'Referrer-Policy', 'no-referrer'"); + expect(indexCode).toContain('Strict-Transport-Security'); + }); +});