修复认证挑战与可信代理安全边界(07-17-auth-challenge-trusted-proxy-hardening)
这个提交包含在:
@@ -6,6 +6,12 @@ const rootDir = path.resolve(__dirname, '..', '..');
|
||||
const configPath = path.join(rootDir, 'server/src/config.js');
|
||||
const envExamplePath = path.join(rootDir, 'deploy/trialrun/.env.example');
|
||||
const composePath = path.join(rootDir, 'deploy/trialrun/docker-compose.yml');
|
||||
const runbookPath = path.join(rootDir, 'deploy/trialrun/runbook.md');
|
||||
const checklistPath = path.join(rootDir, 'deploy/trialrun/checklist.md');
|
||||
const nginxPaths = [
|
||||
path.join(rootDir, 'deploy/trialrun/nginx-hyb-oa.conf'),
|
||||
path.join(rootDir, 'deploy/trialrun/nginx.conf.example'),
|
||||
];
|
||||
|
||||
const requiredServices = ['server', 'worker', 'init'];
|
||||
const deploymentInjectedEnvKeys = new Set(['BUILD_SHA']);
|
||||
@@ -122,6 +128,12 @@ function fail(message, errors) {
|
||||
const configContent = readFile(configPath);
|
||||
const envExampleContent = readFile(envExamplePath);
|
||||
const composeContent = readFile(composePath);
|
||||
const runbookContent = readFile(runbookPath);
|
||||
const checklistContent = readFile(checklistPath);
|
||||
const nginxContents = nginxPaths.map((filePath) => ({
|
||||
filePath,
|
||||
content: readFile(filePath),
|
||||
}));
|
||||
|
||||
const configKeys = extractConfigEnvKeys(configContent);
|
||||
const envExampleKeys = extractEnvExampleKeys(envExampleContent);
|
||||
@@ -172,6 +184,51 @@ for (const serviceName of requiredServices) {
|
||||
}
|
||||
}
|
||||
|
||||
const serverBlock = extractServiceBlock(composeContent, 'server');
|
||||
if (!serverBlock || !/^\s{6}-\s*["']127\.0\.0\.1:3000:3000["']\s*$/m.test(serverBlock)) {
|
||||
errors.push('服务 server 的 3000 端口必须只绑定宿主回环地址');
|
||||
}
|
||||
|
||||
for (const { filePath, content } of nginxContents) {
|
||||
const relativePath = path.relative(rootDir, filePath);
|
||||
if (content.includes('$proxy_add_x_forwarded_for')) {
|
||||
errors.push(`${relativePath} 不得追加客户端提供的 X-Forwarded-For`);
|
||||
}
|
||||
|
||||
const forwardedForDirectives = [
|
||||
...content.matchAll(/proxy_set_header\s+X-Forwarded-For\s+([^;]+);/g),
|
||||
];
|
||||
if (
|
||||
forwardedForDirectives.length === 0 ||
|
||||
forwardedForDirectives.some((match) => match[1].trim() !== '$remote_addr')
|
||||
) {
|
||||
errors.push(`${relativePath} 必须用 $remote_addr 覆盖 X-Forwarded-For`);
|
||||
}
|
||||
}
|
||||
|
||||
const authChallengeEndpoints = [
|
||||
'/api/auth/forgot-password',
|
||||
'/api/auth/reset-password',
|
||||
'/api/auth/email-login/code',
|
||||
'/api/auth/email-login/verify',
|
||||
];
|
||||
for (const [name, content] of [
|
||||
['runbook.md', runbookContent],
|
||||
['checklist.md', checklistContent],
|
||||
]) {
|
||||
if (!content.includes('EXTERNAL_GATED')) {
|
||||
errors.push(`${name} 必须标记认证 challenge 边缘操作为 EXTERNAL_GATED`);
|
||||
}
|
||||
for (const endpoint of authChallengeEndpoints) {
|
||||
if (!content.includes(endpoint)) {
|
||||
errors.push(`${name} 缺少认证 challenge 边缘关闭端点 ${endpoint}`);
|
||||
}
|
||||
}
|
||||
if (!/脚本不会自动(?:修改|改)边缘/.test(content)) {
|
||||
errors.push(`${name} 必须声明部署脚本不会自动修改边缘配置`);
|
||||
}
|
||||
}
|
||||
|
||||
if (errors.length) {
|
||||
fail('部署环境契约校验失败', errors);
|
||||
}
|
||||
|
||||
在新工单中引用
屏蔽一个用户