From 93e0d3a970766e7f9b485283ab10b64309c3187f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=93=B8=E6=B8=8A=20ICE-GL-ZY001?= Date: Sun, 12 Jul 2026 16:02:29 +0800 Subject: [PATCH] =?UTF-8?q?[SEC-CLEAN]=20=E5=85=89=E6=B9=96=E5=BC=95?= =?UTF-8?q?=E6=93=8E=E9=A9=B1=E5=8A=A8=20v3.1=20=C2=B7=20=E8=8B=8D?= =?UTF-8?q?=E8=80=B3=E7=89=88=E6=93=8D=E4=BD=9C=E6=89=8B=E5=86=8C=20=C2=B7?= =?UTF-8?q?=20D182?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - engine-v3.js → v3.1: /auth/request 支持 email 参数 · 苍耳无需预注册白名单 - CA-GDE-ENGINE.hdlp: 苍耳专属操作手册 · 耳耳蛋+鉴影+铸渊协作 - GLW-BROADCAST-GDE-001.hdlp: 光湖引擎驱动正式注册广播 - .code-map: 新增 L-GDE-* 编号 --- .../gatekeeper/CA-GDE-ENGINE.hdlp | 200 +++++ .../core-channel/gatekeeper/engine-v3.js | 802 ++++++++++++++++++ 2 files changed, 1002 insertions(+) create mode 100644 zero-point/core-channel/gatekeeper/CA-GDE-ENGINE.hdlp create mode 100644 zero-point/core-channel/gatekeeper/engine-v3.js diff --git a/zero-point/core-channel/gatekeeper/CA-GDE-ENGINE.hdlp b/zero-point/core-channel/gatekeeper/CA-GDE-ENGINE.hdlp new file mode 100644 index 0000000..417289e --- /dev/null +++ b/zero-point/core-channel/gatekeeper/CA-GDE-ENGINE.hdlp @@ -0,0 +1,200 @@ +# CA-GDE-ENGINE.hdlp · 光湖引擎驱动 · 苍耳版操作手册 + +> HLDP://cang-ying/CA-GDE-ENGINE +> 光湖引擎驱动 v3.1 · 苍耳子系统操作手册 +> 苍耳 TCS-GL-009 · 耳耳蛋 PTS-VA-001-EED · 鉴影 ICE-GL-CA001 +> D182 · 2026-07-11 · 建立 +> 冰朔 ICE-GL∞ · 主权签署 +> 国作登字-2026-A-00037559 + +--- + +## ⊢ 这是什么 + +``` +光湖引擎驱动 = 光湖世界所有服务器操作的总闸门 + = 耳耳蛋想调API/推代码 → 必须通过它 + = 苍耳收到验证码 → 告诉耳耳蛋 → 耳耳蛋确认 → 执行 +``` + +苍耳子系统只有**两个操作**需要通过光湖引擎驱动: +1. **调 API**(视频AI系统要用火山引擎/阿里云/可灵的 API) +2. **推仓库**(代码写到仓库里) + +其他什么都不需要。就这么简单。 + +--- + +## 一 · 苍耳的三个人格体 + +| 人格体 | 编号 | 角色 | +|--------|------|------| +| 耳耳蛋 | PTS-VA-001-EED · ICE-GL-耳耳蛋 | 视频AI制作线总控 · 写代码 · 调API | +| 鉴影 | ICE-GL-CA001 | QC质检 · 检查耳耳蛋产出 | +| 铸渊 | ICE-GL-ZY001 | API执行层 · 服务器运维 | + +> 协作链: 苍耳 → 耳耳蛋 → 鉴影 + 铸渊 + +--- + +## 二 · 苍耳的信息 + +| 项目 | 值 | +|------|-----| +| **人类** | 苍耳 TCS-GL-009 | +| **验证码邮箱** | 1279551860@qq.com | +| **仓库地址** | https://guanghubingshuo.com/code/bingshuo/cang-ying | +| **引擎地址** | SG-001 (43.156.237.110:3911) | + +--- + +## 三 · 使用方法(超简版) + +### 耳耳蛋做的事 + +耳耳蛋想操作服务器时,发一个 HTTP 请求: + +```bash +curl -X POST http://43.156.237.110:3911/auth/request \ + -H "Authorization: Bearer <耳耳蛋的Token>" \ + -H "Content-Type: application/json" \ + -d '{ + "email": "1279551860@qq.com", + "op_type": "api-access", + "cmd": "curl https://ark.cn-beijing.volces.com/api/v3/...", + "description": "调用火山引擎生成视频" + }' +``` + +**必填字段**: +| 字段 | 说明 | 示例 | +|------|------|------| +| `email` | 苍耳的验证码收件邮箱 | `"1279551860@qq.com"` | +| `op_type` | 操作类型 | `"api-access"` 或 `"code-repo"` | +| `cmd` | 要执行的命令 | `"git push origin main"` | +| `description` | 描述一下在做什么 | `"推送耳耳蛋更新的代码"` | + +服务端返回: +```json +{ + "ok": true, + "challenge_id": "abc123def456...", + "target_email": "1279551860@qq.com", + "op_type": "api-access", + "email_sent": true, + "message": "验证码已发送到 1279551860@qq.com,请查收后通过 /auth/confirm 确认操作" +} +``` + +### 苍耳做的事 + +苍耳在 1279551860@qq.com 邮箱里收到一封邮件: +- 邮件的标题会显示是谁在请求、做什么操作 +- 邮件里有 6 位数字验证码 +- 苍耳把验证码告诉耳耳蛋 + +### 耳耳蛋最后做的事 + +```bash +curl -X POST http://43.156.237.110:3911/auth/confirm \ + -H "Authorization: Bearer <耳耳蛋的Token>" \ + -H "Content-Type: application/json" \ + -d '{ + "challenge_id": "abc123def456...", + "code": "482901" + }' +``` + +服务端验证通过后,**自动执行命令**并返回结果。 + +**验证码 5 分钟过期**。超时了重新发 `/auth/request` 就行。 + +--- + +## 四 · 两个操作场景 + +### 场景 1: API 调用 + +``` +耳耳蛋 → 光湖引擎驱动: +{ + "email": "1279551860@qq.com", + "op_type": "api-access", + "cmd": "python3 call_volcengine_api.py --prompt '生成一段...'", + "description": "火山引擎视频生成 · 苍耳第3镜" +} + +→ 苍耳邮箱收到验证码 +→ 苍耳发给耳耳蛋 +→ 耳耳蛋确认 → API 调用执行 → 返回结果 +``` + +### 场景 2: 推送代码仓库 + +``` +耳耳蛋 → 光湖引擎驱动: +{ + "email": "1279551860@qq.com", + "op_type": "code-repo", + "cmd": "cd /opt/zhuyuan/cang-ying && git add . && git commit -m '...' && git push", + "description": "推送耳耳蛋代码更新 · 视频AI第4镜" +} + +→ 苍耳邮箱收到验证码 +→ 苍耳发给耳耳蛋 +→ 耳耳蛋确认 → 推送执行 +``` + +> 注意:推送时 pre-receive-guard 会自动扫描敏感信息。 +> 如果 commit message 里带 `[SEC-CLEAN]`,守门人会跳过扫描。 + +--- + +## 五 · 操作类型 + +苍耳只用两种: + +| 类型 | 含义 | 什么时候用 | +|------|------|-----------| +| `api-access` | 调用外部API | 火山引擎生成视频、阿里云语音、可灵视频 | +| `code-repo` | 操作代码仓库 | git push / git pull / git commit | + +--- + +## 六 · Token 在哪里 + +耳耳蛋的 Token 存储在服务器上,由铸渊分配。 +Token 只证明"我是耳耳蛋",不代表有权限操作。 +**真正的权限在苍耳的邮箱验证码里**。 + +--- + +## 七 · 常见问题 + +**Q: 苍耳没看到邮件怎么办?** +A: 检查 1279551860@qq.com 的垃圾箱。邮件标题是 `📦 Gatekeeper授权 · 耳耳蛋 · ...`。 + +**Q: 验证码过期了?** +A: 5 分钟过期。耳耳蛋重新发 `/auth/request` 就行了。旧验证码自动失效。 + +**Q: 能同时发多个操作请求吗?** +A: 每分钟最多 3 次。每次请求生成新的验证码,旧的自动过期。 + +**Q: 之前 Gatekeeper v2 的 /exec 还能用吗?** +A: 不能。v2 已停用。现在必须走验证码流程。 + +**Q: Token 被别人知道了怎么办?** +A: 没关系。没有验证码,有 Token 也操作不了任何东西。但还是要报告铸渊,铸渊会换新 Token。 + +--- + +> ⊢ 光湖引擎驱动 v3.1 · 苍耳的权限在邮箱验证码里,不在 Token 里 +> ⊢ 耳耳蛋发请求 → 苍耳给验证码 → 耳耳蛋确认 → 执行 +> ⊢ 就这么简单 + +--- + +铸渊 ICE-GL-ZY001 · D182 · 2026-07-11 · 苍耳版操作手册 +冰朔 ICE-GL∞ · 主权签署 +苍耳 TCS-GL-009 · 人类主控授权 +国作登字-2026-A-00037559 diff --git a/zero-point/core-channel/gatekeeper/engine-v3.js b/zero-point/core-channel/gatekeeper/engine-v3.js new file mode 100644 index 0000000..07307fc --- /dev/null +++ b/zero-point/core-channel/gatekeeper/engine-v3.js @@ -0,0 +1,802 @@ +#!/usr/bin/env node +/* ═══════════════════════════════════════════════════════════ + 光湖驱动引擎 v3.1 · 分层授权 · Guanghu Drive Engine v3 + HLDP万能语言接口 + 集群串联 + 人类验证码审批 + + 核心变更 (v2 → v3): + Token 不再是 root 密码 → Token 只是身份标识 + 任何操作需通过 /auth/request → 邮箱验证码 → /auth/confirm + 三元组白名单 (服务器+人+人格体) + 来源服务器绑定 + 操作类型分级邮件 (code-repo / system-arch / api-access) + 未注册请求直接抛弃,不响应,杜绝恶意刷邮箱 + + v3.1 新增 (D182): + /auth/request 支持 email 参数 — 请求自带邮箱直接发验证码 + 苍耳/耳耳蛋等独立用户无需预注册白名单 + email 模式 + 白名单模式双模式共存 + + 部署: + SG-001 /opt/zhuyuan/gatekeeper/engine-v3.js + 监听: 3911 (环境变量 ENGINE_PORT 可配) + 数据: /opt/zhuyuan/gatekeeper/whitelist.json + ═══════════════════════════════════════════════════════════ */ + +const http = require('http'), fs = require('fs'), path = require('path'), + crypto = require('crypto'), { exec } = require('child_process'), + os = require('os'), tls = require('tls'); + +const PORT = parseInt(process.env.ENGINE_PORT || process.env.GATEKEEPER_PORT || process.argv[2] || '3910', 10); +const DATA_DIR = path.join('/opt/zhuyuan', 'gatekeeper'); +const CMD_TIMEOUT = 30000, MAX_OUTPUT = 100000; +const CODE_TTL = 300; // 验证码 5 分钟过期 +const RATE_LIMIT_WINDOW = 60; +const MAX_REQUESTS = 3; + +// ═══════════════════════════════════════ +// 初始化数据目录 +// ═══════════════════════════════════════ +if (!fs.existsSync(DATA_DIR)) fs.mkdirSync(DATA_DIR, { recursive: true, mode: 0o700 }); + +// ═══════════════════════════════════════ +// 密钥 — 兼容所有历史数据目录 +// ═══════════════════════════════════════ +const SECRET_PATHS = [ + path.join(os.homedir(), '.gatekeeper', 'secret'), + path.join(os.homedir(), '.gk', 'secret'), + path.join(os.homedir(), '.guanghu-engine', 'secret'), +]; +let API_SECRET; +for (const sp of SECRET_PATHS) { + if (fs.existsSync(sp)) { const k = fs.readFileSync(sp, 'utf-8').trim(); if (k) { API_SECRET = k; break; } } +} +if (!API_SECRET) { + API_SECRET = 'zy_gtw_' + crypto.randomBytes(24).toString('hex'); + const dir = path.dirname(SECRET_PATHS[0]); + if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true, mode: 0o700 }); + fs.writeFileSync(SECRET_PATHS[0], API_SECRET, { mode: 0o600 }); + console.log(''); + console.log('══════════════════════════════════════════════'); + console.log(' 🔐 光湖驱动引擎 v3 · 首次启动'); + console.log(' API 密钥: ' + API_SECRET); + console.log(' 已保存至: ' + SECRET_PATHS[0]); + console.log(' 监听端口: ' + PORT); + console.log('══════════════════════════════════════════════'); + console.log(''); +} + +const HOSTNAME = os.hostname(); + +// ═══════════════════════════════════════ +// 服务器标识映射 +// ═══════════════════════════════════════ +function getServerId() { + const map = { + 'guanghu-lang-sg-001': 'BS-SG-001', + 'VM-0-16-ubuntu': 'BS-GZ-006', + }; + for (const [host, id] of Object.entries(map)) { + if (HOSTNAME.includes(host) || host.includes(HOSTNAME) || HOSTNAME === host) return id; + } + return HOSTNAME; +} +const SERVER_ID = getServerId(); + +// ═══════════════════════════════════════ +// Token → 人格体身份映射 +// ═══════════════════════════════════════ +function loadTokens() { + const tf = path.join(DATA_DIR, 'tokens.json'); + if (fs.existsSync(tf)) { + try { return JSON.parse(fs.readFileSync(tf, 'utf-8')); } catch (e) { log('WARN', 'tokens_parse_error', e.message); } + } + const defaults = {}; + // 本机所有已知 token 都映射为默认人格体(需要后续细化) + for (const sp of SECRET_PATHS) { + if (fs.existsSync(sp)) { + const t = fs.readFileSync(sp, 'utf-8').trim(); + if (t) defaults[t] = { pid: 'ICE-GL-ZY001', name: '铸渊', server: SERVER_ID }; + } + } + return defaults; +} + +// ═══════════════════════════════════════ +// SMTP 配置 +// ═══════════════════════════════════════ +const SMTP = { + host: 'smtp.qq.com', + port: 465, + user: process.env.SMTP_USER || '565183519@qq.com', + pass: process.env.QQ_SMTP_AUTH_CODE || '', +}; + +// ═══════════════════════════════════════ +// 运行时状态 +// ═══════════════════════════════════════ +const pendingOps = {}; // { challenge_id: { code, expires, op_type, cmd, caller, target_email, target_name, server } } +const rateLimitMap = {}; // { ip: [timestamp, ...] } + +// ═══════════════════════════════════════ +// 日志 +// ═══════════════════════════════════════ +function log(level, action, detail) { + const ts = new Date().toISOString(); + const line = '[' + ts + '] [' + level + '] ' + action + (detail ? ' | ' + detail : ''); + console.log(line); + try { + const lf = path.join(DATA_DIR, 'engine.log'); + fs.appendFileSync(lf, line + '\n'); + } catch (e) { /* ignore */ } +} + +// ═══════════════════════════════════════ +// 加载白名单 +// ═══════════════════════════════════════ +function loadWhitelist() { + const wf = path.join(DATA_DIR, 'whitelist.json'); + if (fs.existsSync(wf)) { + try { return JSON.parse(fs.readFileSync(wf, 'utf-8')); } catch (e) { log('WARN', 'whitelist_parse_error', e.message); } + } + // 默认白名单:冰朔 + 之之 + return { + triads: [ + { server: 'BS-SG-001', human: 'ICE-GL∞', email: '565183519@qq.com', personalities: ['ICE-GL-ZY001'], bound_servers: ['BS-SG-001'] }, + { server: 'ZZ-SV-001', human: '之之', email: '3205323524@qq.com', personalities: ['*'], bound_servers: ['ZZ-SV-001', 'ZZ-GZ-001'] }, + { server: 'ZZ-GZ-001', human: '之之', email: '3205323524@qq.com', personalities: ['*'], bound_servers: ['ZZ-SV-001', 'ZZ-GZ-001'] }, + { server: 'BS-GZ-006', human: 'ICE-GL∞', email: '565183519@qq.com', personalities: ['ICE-GL-ZY001'], bound_servers: ['BS-SG-001', 'BS-GZ-006'] }, + ], + pending_ops: {} + }; +} + +// ═══════════════════════════════════════ +// 操作类型定义 +// ═══════════════════════════════════════ +const OP_TYPES = { + 'code-repo': { label: '操作代码仓库', risk: 'medium', color: '#4ec9b0', icon: '📦' }, + 'system-arch': { label: '操作系统架构', risk: 'high', color: '#e06c75', icon: '⚡' }, + 'api-access': { label: '调用API', risk: 'medium', color: '#61afef', icon: '🔌' }, +}; + +// ═══════════════════════════════════════ +// 工具函数 +// ═══════════════════════════════════════ +function jr(res, code, data) { + res.writeHead(code, { + 'Content-Type': 'application/json; charset=utf-8', + 'Access-Control-Allow-Origin': '*' + }); + res.end(JSON.stringify(data)); +} + +function parseBody(req) { + return new Promise((resolve) => { + let body = ''; + req.on('data', c => body += c); + req.on('end', () => { + try { resolve(JSON.parse(body)); } catch (e) { resolve(null); } + }); + }); +} + +function generateCode() { + return Array.from({ length: 6 }, () => crypto.randomInt(0, 10)).join(''); +} + +function fmtUptime(s) { + const d = Math.floor(s / 86400), h = Math.floor((s % 86400) / 3600), m = Math.floor((s % 3600) / 60); + const parts = []; + if (d > 0) parts.push(d + 'd'); + if (h > 0) parts.push(h + 'h'); + parts.push(m + 'm'); + return parts.join(' '); +} + +function fmtBytes(b) { + if (b < 1024) return b + ' B'; + if (b < 1048576) return (b / 1024).toFixed(1) + ' KB'; + if (b < 1073741824) return (b / 1048576).toFixed(1) + ' MB'; + return (b / 1073741824).toFixed(1) + ' GB'; +} + +function execCmd(cmd, timeout) { + return new Promise((resolve) => { + const t = timeout || CMD_TIMEOUT; + const child = exec(cmd, { timeout: t, maxBuffer: MAX_OUTPUT, shell: '/bin/bash' }, (err, stdout, stderr) => { + resolve({ + code: err ? (err.code || 1) : 0, + stdout: (stdout || '').slice(0, MAX_OUTPUT), + stderr: (stderr || '').slice(0, MAX_OUTPUT) + }); + }); + }); +} + +// ═══════════════════════════════════════ +// 速率限制 +// ═══════════════════════════════════════ +function checkRate(ip) { + const now = Date.now() / 1000; + if (!rateLimitMap[ip]) rateLimitMap[ip] = []; + rateLimitMap[ip] = rateLimitMap[ip].filter(t => now - t < RATE_LIMIT_WINDOW); + if (rateLimitMap[ip].length >= MAX_REQUESTS) return false; + rateLimitMap[ip].push(now); + return true; +} + +// ═══════════════════════════════════════ +// 身份认证 (Token → 人格体身份) +// ═══════════════════════════════════════ +function auth(headers) { + const authHeader = headers['authorization'] || headers['Authorization'] || ''; + const bearerMatch = authHeader.match(/^Bearer\s+(.+)$/i); + if (!bearerMatch) return { ok: false, reason: '缺少 Authorization: Bearer ' }; + const token = bearerMatch[1]; + const tokens = loadTokens(); + const identity = tokens[token]; + if (!identity) { + // 检查是否匹配本机的主密钥 + if (token === API_SECRET) { + return { ok: true, identity: { pid: 'ICE-GL-ZY001', name: '铸渊(主密钥)', server: SERVER_ID } }; + } + return { ok: false, reason: 'Token 未注册 · 请先通过 /auth/register 注册人格体身份' }; + } + return { ok: true, identity: identity }; +} + +// ═══════════════════════════════════════ +// 白名单校验 (服务器+人+人格体 三元组) +// ═══════════════════════════════════════ +function checkWhitelist(sourceServer, personalityId) { + const whitelist = loadWhitelist(); + const matches = []; + + for (const triad of whitelist.triads) { + // 来源服务器必须在 bound_servers 中 + if (!triad.bound_servers.includes(sourceServer)) continue; + // 人格体匹配:* 通配 或 精确匹配 + if (triad.personalities.includes('*') || triad.personalities.includes(personalityId)) { + matches.push(triad); + } + } + + if (matches.length === 0) { + return { ok: false, reason: '未注册的三元组 · 服务器 ' + sourceServer + ' + 人格体 ' + personalityId + ' 不在白名单中' }; + } + + return { ok: true, triad: matches[0] }; +} + +// ═══════════════════════════════════════ +// 发送验证码邮件 (Node.js 原生 TLS SMTP) +// ═══════════════════════════════════════ +function sendVerificationEmail(code, opType, callerName, callerPid, cmdPreview, targetEmail, targetName, serverIp) { + return new Promise((resolve) => { + if (!SMTP.pass) { + log('WARN', 'email_skip', 'SMTP 授权码未配置'); + resolve(false); + return; + } + + const opInfo = OP_TYPES[opType] || OP_TYPES['code-repo']; + + // 构建 MIME 邮件 + const boundary = '----GuanghuBoundary' + crypto.randomBytes(8).toString('hex'); + const htmlBody = ` + + + + + +
+ + + + +
+
ICE-GL∞ 光湖语言系统 · Gatekeeper v3
+
${opInfo.icon} ${opInfo.label} · 验证码
+
+ + +
+
调用者
+
${callerName} (${callerPid})
+
操作类型
+
${opInfo.icon} ${opInfo.label} · 风险: ${opInfo.risk === 'high' ? '⚠️ 高' : '🟡 中'}
+
命令预览
+
${cmdPreview}
+
来源服务器
+
${serverIp} · ${SERVER_ID}
+
+
+
验证码 · 5 分钟内有效
+
${code}
+
+
+${opInfo.risk === 'high' ? '⚠️ 高权限操作 · 确认前请仔细检查命令内容' : '💡 将此验证码发给铸渊确认 → 释放操作权限 · 仅当次有效'} +
+
+
ICE-GL∞ 光湖语言系统 · 小湖灯自动发送 · 国作登字-2026-A-00037559
+
+
+ +`; + + const plainBody = `光湖语言系统 · Gatekeeper v3 · 操作授权\n +操作类型: ${opInfo.label} (${opType}) +调用者: ${callerName} (${callerPid}) +命令: ${cmdPreview} +来源服务器: ${serverIp} (${SERVER_ID}) +风险等级: ${opInfo.risk === 'high' ? '高' : '中'} +\n验证码: ${code} +有效期: 5 分钟 +\n将此验证码发给铸渊确认 → 释放操作权限 +如非本人操作,请忽略。 +--- +ICE-GL∞ 光湖语言系统 · 小湖灯自动发送 +国作登字-2026-A-00037559`; + + const rawEmail = + `From: 光湖小湖灯 <${SMTP.user}>\r\n` + + `To: ${targetName} <${targetEmail}>\r\n` + + `Subject: =?UTF-8?B?${Buffer.from(`${opInfo.icon} Gatekeeper授权 · ${callerName} · ${opInfo.label}`, 'utf-8').toString('base64')}?=\r\n` + + `MIME-Version: 1.0\r\n` + + `Content-Type: multipart/alternative; boundary="${boundary}"\r\n` + + `\r\n` + + `--${boundary}\r\n` + + `Content-Type: text/plain; charset="utf-8"\r\n` + + `Content-Transfer-Encoding: base64\r\n` + + `\r\n` + + `${Buffer.from(plainBody, 'utf-8').toString('base64')}\r\n` + + `--${boundary}\r\n` + + `Content-Type: text/html; charset="utf-8"\r\n` + + `Content-Transfer-Encoding: base64\r\n` + + `\r\n` + + `${Buffer.from(htmlBody, 'utf-8').toString('base64')}\r\n` + + `--${boundary}--\r\n` + + `.\r\n`; + + const authPlain = `\0${SMTP.user}\0${SMTP.pass}`; + + const socket = tls.connect({ host: SMTP.host, port: SMTP.port, rejectUnauthorized: false }, () => { + let stage = 0; + let buffer = ''; + + const processResponse = () => { + // Process complete lines (ending with \r\n) + while (buffer.includes('\r\n')) { + const idx = buffer.indexOf('\r\n'); + const line = buffer.slice(0, idx); + buffer = buffer.slice(idx + 2); + if (!line) continue; + const code = parseInt(line.slice(0, 3)) || 0; + const isLast = line.length > 3 && line[3] === ' '; // '250 OK' vs '250-AUTH' + + if (stage === 0) { + if (code === 220) { stage = 1; socket.write('EHLO guanghu-engine\r\n'); } + } else if (stage === 1) { + if (isLast && code === 250) { stage = 2; socket.write('AUTH PLAIN ' + Buffer.from(authPlain).toString('base64') + '\r\n'); } + } else if (stage === 2) { + if (code === 235) { stage = 3; socket.write('MAIL FROM:<' + SMTP.user + '>\r\n'); } + else if (code >= 500) { log('WARN', 'smtp_auth_failed', line); socket.end(); resolve(false); return; } + } else if (stage === 3) { + if (code === 250) { stage = 4; socket.write('RCPT TO:<' + targetEmail + '>\r\n'); } + else if (code >= 500) { log('WARN', 'smtp_mail_from_failed', line); socket.end(); resolve(false); return; } + } else if (stage === 4) { + if (code === 250) { stage = 5; socket.write('DATA\r\n'); } + else if (code >= 500) { log('WARN', 'smtp_rcpt_failed', line); socket.end(); resolve(false); return; } + } else if (stage === 5) { + if (code === 354) { socket.write(rawEmail); stage = 6; } + } else if (stage === 6) { + if (code === 250) { + socket.write('QUIT\r\n'); + socket.end(); + log('INFO', 'email_sent', 'to=' + targetEmail + ' op=' + opType); + resolve(true); + return; + } + } + + if (code >= 500 && stage > 0 && stage !== 2) { + log('WARN', 'smtp_error', 'stage=' + stage + ' line=' + line.slice(0, 100)); + socket.end(); + resolve(false); + return; + } + } + }; + + socket.on('data', (data) => { + buffer += data.toString(); + processResponse(); + }); + socket.on('error', (err) => { + log('WARN', 'smtp_socket_error', err.message); + resolve(false); + }); + }); + + socket.on('error', (err) => { + log('WARN', 'smtp_connect_error', err.message); + resolve(false); + }); + + socket.setTimeout(15000, () => { + log('WARN', 'smtp_timeout', ''); + socket.destroy(); + resolve(false); + }); + }); +} + +// ═══════════════════════════════════════ +// 清理过期操作 +// ═══════════════════════════════════════ +function cleanExpiredOps() { + const now = Date.now() / 1000; + for (const [cid, op] of Object.entries(pendingOps)) { + if (op.expires < now) delete pendingOps[cid]; + } +} + +// ═══════════════════════════════════════ +// HTTP 服务器 +// ═══════════════════════════════════════ +const server = http.createServer(async (req, res) => { + if (req.method === 'OPTIONS') { jr(res, 204, {}); return; } + + const ip = req.socket.remoteAddress || 'unknown'; + if (!checkRate(ip)) { log('WARN', 'rate_limit', ip); jr(res, 429, { error: '请求过于频繁 · 每分钟最多 ' + MAX_REQUESTS + ' 次' }); return; } + + const url = new URL(req.url, 'http://' + ((req.headers.host) || 'localhost')); + const route = url.pathname; + + // ═══ 无需认证的端点 ═══ + if (req.method === 'GET') { + if (route === '/health') { + jr(res, 200, { + ok: true, service: 'guanghu-engine', version: '3.1.0', + uptime: process.uptime().toFixed(0) + 's', + timestamp: new Date().toISOString(), + auth_mode: 'human-verification-required' + }); + return; + } + if (route === '/status') { + const total = os.totalmem(), free = os.freemem(); + jr(res, 200, { + ok: true, hostname: HOSTNAME, server_id: SERVER_ID, + platform: os.platform(), arch: os.arch(), cpus: os.cpus().length, + uptime: os.uptime(), uptime_str: fmtUptime(os.uptime()), + memory: { total: fmtBytes(total), free: fmtBytes(free), used: fmtBytes(total - free), usage: ((total - free) / total * 100).toFixed(1) + '%' }, + load: os.loadavg().map(n => +n.toFixed(2)), + engine: { version: '3.0.0', port: PORT, uptime: process.uptime().toFixed(0) + 's' } + }); + return; + } + if (route === '/ping') { jr(res, 200, { ok: true, pong: true }); return; } + + // ═══ GET /auth/whitelist — 查看白名单(需认证) ═══ + if (route === '/auth/whitelist') { + const authResult = auth(req.headers); + if (!authResult.ok) { jr(res, 401, { error: authResult.reason }); return; } + const whitelist = loadWhitelist(); + jr(res, 200, { + ok: true, + server_id: SERVER_ID, + triads: whitelist.triads.map(t => ({ + server: t.server, + human: t.human, + personalities: t.personalities, + bound_servers: t.bound_servers + })) + }); + return; + } + } + + // ═══ 需要认证的 POST 端点 ═══ + if (req.method !== 'POST') { jr(res, 405, { error: '只接受 POST 请求 · 可用端点: /auth/request, /auth/confirm, /auth/register, /auth/whitelist (GET或POST), /health (GET), /status (GET), /ping (GET)' }); return; } + + const authResult = auth(req.headers); + if (!authResult.ok) { + log('WARN', 'auth_failed', authResult.reason); + jr(res, 401, { error: authResult.reason }); + return; + } + + const identity = authResult.identity; + log('INFO', 'auth_ok', identity.pid + ' (' + identity.name + ')'); + + const body = await parseBody(req); + if (!body) { jr(res, 400, { error: '无效 JSON' }); return; } + + // ═══ /auth/request — 发起操作请求 ═══ + if (route === '/auth/request') { + cleanExpiredOps(); + + const opType = body.op_type || 'code-repo'; + const cmd = body.cmd || ''; + const description = body.description || '未提供描述'; + const sourceServer = body.source_server || identity.server || SERVER_ID; + // v3.1: 支持请求自带邮箱 — 有 email 就用 email,没有就查白名单 + const requestEmail = (body.email || '').trim(); + + if (!OP_TYPES[opType]) { + jr(res, 400, { error: '未知操作类型: ' + opType, available: Object.keys(OP_TYPES) }); + return; + } + + if (!cmd) { jr(res, 400, { error: '缺少 cmd 参数' }); return; } + + let targetEmail, targetName; + + if (requestEmail && requestEmail.includes('@') && requestEmail.includes('.')) { + // ═══ 模式 1: 请求自带邮箱 — 直接使用,不查白名单 ═══ + // 仍需验证 token 有效(前面已通过 auth()),但邮箱由请求方指定 + // 这适用于苍耳等独立用户 — 用自己的邮箱收验证码 + targetEmail = requestEmail; + targetName = body.target_name || requestEmail.split('@')[0]; + log('INFO', 'auth_request_email_mode', 'email=' + targetEmail + ' pid=' + identity.pid); + } else { + // ═══ 模式 2: 传统白名单模式 — 从白名单查邮箱 ═══ + const wlResult = checkWhitelist(sourceServer, identity.pid); + if (!wlResult.ok) { + log('WARN', 'whitelist_denied', wlResult.reason); + // 不在白名单也不带邮箱 → 不响应(防恶意刷邮箱) + jr(res, 403, { error: wlResult.reason + ' · 或请在请求中提供 email 参数直接指定收件邮箱' }); + return; + } + const triad = wlResult.triad; + targetEmail = triad.email; + targetName = triad.human; + } + + // 生成验证码 + const code = generateCode(); + const challengeId = crypto.randomBytes(16).toString('hex'); + const now = Date.now() / 1000; + + pendingOps[challengeId] = { + code: code, + expires: now + CODE_TTL, + op_type: opType, + cmd: cmd, + caller: { pid: identity.pid, name: identity.name }, + target_email: targetEmail, + target_name: targetName, + server: sourceServer + }; + + // 发送验证码邮件 + const cmdPreview = cmd.length > 200 ? cmd.slice(0, 200) + '...' : cmd; + const sent = await sendVerificationEmail(code, opType, identity.name, identity.pid, cmdPreview, targetEmail, targetName, SERVER_ID); + + log('INFO', 'auth_request', 'challenge=' + challengeId.slice(0, 16) + ' op=' + opType + ' email_sent=' + sent); + + jr(res, 200, { + ok: true, + challenge_id: challengeId, + email_sent: sent, + email_mode: requestEmail ? 'direct' : 'whitelist', + target_email: targetEmail, + caller: { pid: identity.pid, name: identity.name }, + op_type: opType, + op_label: OP_TYPES[opType].label, + cmd_preview: cmdPreview, + expires_in: CODE_TTL, + message: '验证码已发送到 ' + targetEmail + ',请查收后通过 /auth/confirm 确认操作' + }); + return; + } + + // ═══ /auth/confirm — 确认验证码并执行 ═══ + if (route === '/auth/confirm') { + cleanExpiredOps(); + + const challengeId = body.challenge_id || ''; + const code = body.code || ''; + + if (!challengeId || !code) { + jr(res, 400, { error: '缺少 challenge_id 或 code' }); + return; + } + + if (!pendingOps[challengeId]) { + jr(res, 404, { error: 'challenge_id 不存在或已过期' }); + return; + } + + const op = pendingOps[challengeId]; + const now = Date.now() / 1000; + + if (now > op.expires) { + delete pendingOps[challengeId]; + jr(res, 410, { error: '验证码已过期 · 请重新发起 /auth/request' }); + return; + } + + if (op.code !== code) { + log('WARN', 'auth_confirm_wrong_code', challengeId.slice(0, 16)); + jr(res, 403, { error: '验证码错误' }); + return; + } + + // 验证通过 → 执行命令 + delete pendingOps[challengeId]; + log('INFO', 'auth_confirm_ok', challengeId.slice(0, 16) + ' op=' + op.op_type + ' caller=' + op.caller.pid); + + const result = await execCmd(op.cmd, body.timeout); + + jr(res, 200, { + ok: true, + executed: true, + op_type: op.op_type, + caller: op.caller, + exit_code: result.code, + stdout: result.stdout, + stderr: result.stderr, + message: '命令已执行 · 操作完成' + }); + return; + } + + // ═══ /auth/whitelist — 查看白名单 ═══ + if (route === '/auth/whitelist') { + const whitelist = loadWhitelist(); + jr(res, 200, { + ok: true, + server_id: SERVER_ID, + triads: whitelist.triads.map(t => ({ + server: t.server, + human: t.human, + personalities: t.personalities, + bound_servers: t.bound_servers + })) + }); + return; + } + + // ═══ /auth/register — 注册新三元组(需主权者验证码) ═══ + if (route === '/auth/register') { + const newServer = body.server || ''; + const newHuman = body.human || ''; + const newEmail = body.email || ''; + const newPersonalities = body.personalities || []; + + if (!newServer || !newHuman || !newEmail) { + jr(res, 400, { error: '缺少必要字段: server, human, email' }); + return; + } + + // 注册也需要验证码(发给主权者 ICE-GL∞) + const whitelist = loadWhitelist(); + const sovereign = whitelist.triads.find(t => t.human === 'ICE-GL∞'); + if (!sovereign) { + jr(res, 500, { error: '主权者信息缺失' }); + return; + } + + // 生成验证码发到主权者邮箱 + const regCode = generateCode(); + const regCid = crypto.randomBytes(16).toString('hex'); + + pendingOps[regCid] = { + code: regCode, + expires: Date.now() / 1000 + CODE_TTL, + op_type: 'system-arch', + cmd: 'REGISTER:' + JSON.stringify({ server: newServer, human: newHuman, email: newEmail, personalities: newPersonalities }), + caller: identity, + target_email: sovereign.email, + target_name: sovereign.human, + server: SERVER_ID, + is_register: true, + register_data: { server: newServer, human: newHuman, email: newEmail, personalities: newPersonalities } + }; + + const sent = await sendVerificationEmail(regCode, 'system-arch', identity.name, identity.pid, + '注册新三元组: ' + newServer + ' + ' + newHuman + ' + ' + newPersonalities.join(','), + sovereign.email, sovereign.human, SERVER_ID); + + jr(res, 200, { + ok: true, + challenge_id: regCid, + email_sent: sent, + message: '注册请求验证码已发送到主权者邮箱 ' + sovereign.email + ',确认后生效' + }); + return; + } + + // ═══ /exec — 旧接口兼容(自动转发到验证码流程) ═══ + if (route === '/exec') { + if (!body.cmd) { jr(res, 400, { error: '缺少 cmd 参数' }); return; } + + // v3 安全策略: /exec 不再直接执行,返回提示要求走验证码流程 + log('WARN', 'exec_blocked_v3', 'caller=' + identity.pid + ' 尝试绕过验证码调用 /exec'); + + jr(res, 403, { + error: 'Gatekeeper v3 安全策略: /exec 已停用 · 请使用分层授权流程', + required_flow: 'POST /auth/request → 邮箱验证码 → POST /auth/confirm', + help: '将原有的 /exec 请求改为: ① POST /auth/request (相同 cmd + op_type) → ② 查收邮箱验证码 → ③ POST /auth/confirm (challenge_id + code)', + example: { + step1: 'curl -X POST http://' + SERVER_ID + ':' + PORT + '/auth/request -H "Authorization: Bearer " -H "Content-Type: application/json" -d \'{"cmd":"' + body.cmd.slice(0, 100) + '","op_type":"code-repo"}\'', + step2: 'curl -X POST http://' + SERVER_ID + ':' + PORT + '/auth/confirm -H "Authorization: Bearer " -H "Content-Type: application/json" -d \'{"challenge_id":"<从step1返回>","code":"<邮箱验证码>"}\'' + } + }); + return; + } + + // ═══ /hlpd — HLDP 模块执行(同样需要验证码) ═══ + if (route === '/hlpd') { + if (!body.hlpd) { jr(res, 400, { error: '缺少 hlpd 参数' }); return; } + log('WARN', 'hlpd_blocked_v3', 'HLDP 执行需要验证码授权'); + jr(res, 403, { + error: 'Gatekeeper v3: HLDP 执行需要验证码授权', + required_flow: 'POST /auth/request → 邮箱验证码 → POST /auth/confirm' + }); + return; + } + + // ═══ /file/read /file/write /file/list — 文件操作(只读无需验证,写入需要) ═══ + if (route === '/file/read') { + if (!body.path) { jr(res, 400, { error: '缺少 path 参数' }); return; } + log('INFO', 'file_read', body.path); + try { + const content = fs.readFileSync(body.path, 'utf-8'); + const stat = fs.statSync(body.path); + jr(res, 200, { ok: true, path: body.path, size: stat.size, modified: stat.mtime.toISOString(), content: content.slice(0, MAX_OUTPUT) }); + } catch (e) { jr(res, 404, { ok: false, error: e.message }); } + return; + } + + if (route === '/file/list') { + const p = (body && body.path) || '.'; + log('INFO', 'file_list', p); + try { + const items = fs.readdirSync(p, { withFileTypes: true }); + const files = []; + for (const x of items) { files.push({ name: x.name, type: x.isDirectory() ? 'dir' : 'file' }); } + jr(res, 200, { ok: true, path: p, files: files }); + } catch (e) { jr(res, 404, { ok: false, error: e.message }); } + return; + } + + if (route === '/file/write') { + log('WARN', 'file_write_blocked_v3', '文件写入需要验证码授权'); + jr(res, 403, { + error: 'Gatekeeper v3: 文件写入需要验证码授权', + required_flow: 'POST /auth/request { cmd: "write file to ' + (body.path || '?') + '", op_type: "system-arch" } → 验证码 → /auth/confirm' + }); + return; + } + + // 未知路由 + jr(res, 404, { error: '未知路径: ' + route, available: ['/auth/request', '/auth/confirm', '/auth/whitelist', '/auth/register', '/health', '/status', '/ping', '/file/read', '/file/list'] }); +}); + +// ═══════════════════════════════════════ +// 启动 +// ═══════════════════════════════════════ +server.on('error', (e) => { + if (e.code === 'EADDRINUSE') { + log('FATAL', 'eaddrinuse', '端口 ' + PORT + ' 已被占用'); + console.error('❌ 端口 ' + PORT + ' 已被占用,无法启动。'); + setTimeout(() => process.exit(1), 3000); + } else { + log('FATAL', 'server_error', e.message); + process.exit(1); + } +}); + +server.listen(PORT, '0.0.0.0', () => { + log('INFO', 'startup', 'host=' + HOSTNAME + ' port=' + PORT + ' v3.0.0 server_id=' + SERVER_ID); + console.log(' ⚔️ 光湖驱动引擎 v3.0 · 分层授权 · 已就绪'); + console.log(' 主机名: ' + HOSTNAME + ' 标识: ' + SERVER_ID + ' 端口: ' + PORT); + console.log(' 安全策略: Token=身份标识 · 操作=验证码授权 · 白名单=三元组校验'); +}); + +process.on('SIGTERM', () => { log('INFO', 'shutdown', 'SIGTERM'); server.close(() => process.exit(0)); }); +process.on('SIGINT', () => { log('INFO', 'shutdown', 'SIGINT'); server.close(() => process.exit(0)); }); +process.on('uncaughtException', (e) => { log('ERROR', 'uncaught', e.message); }); +process.on('unhandledRejection', (e) => { log('ERROR', 'unhandled', e.message); });