文件
shanghaipeiwan/server/tests/secure-mode-gates.spec.ts
xcbyy dd836b674e fix(security): 安全闸门与 NODE_ENV 解耦,支付/提现 fail-closed
- 新增 SECURE_MODE(仅 ALLOW_INSECURE_DEV_AUTH=1 时放开):后台登录口令、上传、内容检测、核销码归属全部改为默认收紧,修复线上以 NODE_ENV=development 运行时闸门失效

- 提现审核:未开通真实转账且未显式 ALLOW_SANDBOX_PAY=1 时一律 503,杜绝只改状态不打款

- 预支付:未开启沙箱时不再下发沙箱参数;小程序删除预支付失败伪造沙箱成功的兜底,改为抛错

- 订阅消息支持 MINIPROGRAM_STATE=formal|trial|developer 显式配置,避免真实用户收不到通知

- 动态列表/点赞身份统一 normalizeUserId;文档补充安全闸门与订阅消息环境变量

- 109 套件/720 用例全绿,tsc 构建通过
2026-10-21 11:50:11 +08:00

63 行
3.1 KiB
TypeScript

// 已读取AGENTS.md
import * as fs from 'fs';
import * as path from 'path';
/**
* 第九十三批:安全闸门与 NODE_ENV 解耦 + 支付/提现 fail-closed。
* 背景:线上以 NODE_ENV=development 运行,导致所有 `IS_PRODUCTION && ...` 闸门失效:
* - 后台可用默认口令 admin/admin123 登录;
* - 提现审核通过只改状态、不真实打款;
* - 4 位核销码可被任意登录用户枚举/核销;
* - 匿名可上传、可无限调用内容安全检测。
* 另外小程序在预支付失败时会伪造"沙箱支付成功",必须改为抛错。
*/
describe('第九十三批安全闸门解耦与支付 fail-closed', () => {
const root = path.resolve(__dirname, '../../');
const indexCode = fs.readFileSync(path.join(root, 'server/src/index.ts'), 'utf-8');
const apiCode = fs.readFileSync(path.join(root, 'pwuniapp/common/api.js'), 'utf-8');
const routeBlock = (marker: string, len = 3000) => {
const start = indexCode.indexOf(marker);
expect(start).toBeGreaterThan(-1);
return indexCode.slice(start, start + len);
};
it('安全闸门统一由 SECURE_MODE 控制,不依赖 NODE_ENV', () => {
expect(indexCode).toContain('const SECURE_MODE = !ALLOW_INSECURE_DEV_AUTH;');
expect(routeBlock("app.post('/api/security/msg-sec-check'")).toContain('if (SECURE_MODE)');
expect(indexCode).not.toContain('if (IS_PRODUCTION && !ADMIN_PASSWORD_CONFIGURED)');
expect(indexCode).not.toContain('if (IS_PRODUCTION) {\n try {\n resolveSecureUserId');
});
it('提现审核未开通真实转账时必须 503,禁止只改状态不打款', () => {
const block = routeBlock("app.post('/api/finance/withdrawals/:id/review'", 6000);
expect(block).not.toContain('IS_PRODUCTION && !ALLOW_SANDBOX_PAY');
expect(block).toContain('if (!ALLOW_SANDBOX_PAY)');
expect(block).toContain('微信商家转账未就绪');
});
it('预支付在未开启沙箱时拒绝下发沙箱参数', () => {
const block = routeBlock("app.post('/api/pay/wechat/prepay'", 12000);
const sandboxIdx = block.indexOf("mode: 'SANDBOX'");
expect(sandboxIdx).toBeGreaterThan(-1);
expect(block.slice(0, sandboxIdx)).toContain('if (!ALLOW_SANDBOX_PAY)');
});
it('订阅消息版本支持 MINIPROGRAM_STATE 显式配置', () => {
expect(indexCode).toContain('process.env.MINIPROGRAM_STATE');
expect(indexCode).toContain('const MINIPROGRAM_STATE = (');
expect(indexCode).not.toContain("miniprogram_state: IS_PRODUCTION ? 'formal' : 'developer'");
});
it('小程序预支付失败必须抛错,不得伪造沙箱支付成功', () => {
expect(apiCode).not.toContain('SANDBOX_FALLBACK');
expect(apiCode).toContain('微信支付发起失败,请稍后重试');
expect(apiCode).toMatch(/catch \(err\) \{\n \/\/ 预支付失败必须向上抛出/);
});
it('动态列表与点赞的身份统一走 normalizeUserId', () => {
expect(apiCode).toContain('const uid = normalizeUserId(userId);\n let query = `?userId=${uid}`;');
expect(apiCode).toContain('const uid = normalizeUserId(userId);\n const payload = { userId: uid };');
});
});