diff --git a/dnfs/README.md b/dnfs/README.md index 6e85d32..f46d737 100644 --- a/dnfs/README.md +++ b/dnfs/README.md @@ -59,7 +59,7 @@ Authorization: Bearer _ | `_sp` | 信封字段 | 前端表现 | |-------|---------|---------| | `link` | `url`(http/https,≤2048)、`note?` | favicon 图标(经 static `/api/favicon` 代理拉取)+ 可点击链接 | -| `js` | `code`(≤512KB)、`note?` | "审计源码"(纯文本展示)+ "运行"(插入页面 iframe 沙箱执行,捕获 console 输出)按钮 | +| `js` | `code`(≤512KB)、`note?` | "审计源码"(纯文本展示)+ "运行"(在当前页面上下文执行,可施加特效;console 输出捕获到下方)按钮 | | `image` | `name`、`data`(`data:image/` DataURL,≤3.5M 字符)、`note?` | 图片直接显示 | | `file` | `name`、`mime`、`data`(Base64,≤3.5M 字符)、`note?` | 下载按钮(Base64→Blob→下载) | diff --git a/static/README.md b/static/README.md index f3664b4..7b156d4 100644 --- a/static/README.md +++ b/static/README.md @@ -81,7 +81,7 @@ static :9200 (Flask + CORS) - 流媒体:视频(MP4/WebM/OGG/AVI/MOV/MKV)与音频(MP3/WAV/OGG/FLAC/AAC/M4A)在线播放,支持进度拖动 - 深色模式:`css/theme.css` 统一主题,自动适配系统偏好 - 表单组件:注册页图形/邮箱验证码统一 `.code-box` 行内布局(文字标签 + 输入框 + 控件同排,窄屏自动换行),验证码图片样式复用 `theme.css` 的 `.captcha-img` 类 -- 特殊节点(dnfs 管理员专属):信封 JSON `{"_sp":...}` 内容差异化渲染 —— 链接(favicon 卡片)/ JS 代码(可审计/可运行,插入页面 iframe 沙箱执行)/ 图片(直显)/ 文件(下载);发布权限由 dnfs 后端按 `is_admin` 强制 +- 特殊节点(dnfs 管理员专属):信封 JSON `{"_sp":...}` 内容差异化渲染 —— 链接(favicon 卡片)/ JS 代码(可审计/可运行,在当前页面执行可加特效)/ 图片(直显)/ 文件(下载);发布权限由 dnfs 后端按 `is_admin` 强制 - aibox Web 客户端:`aibox.html` 移动端适配聊天页,经 gateway `[WsProxy]` 反向代理(如 `/ws/aibox → ws://127.0.0.1:8765`)与 aibox 智能体通信,握手由 gateway 强制 `is_admin` 验签;协议与 BotOps/client.py 兼容(chunk 流式 / reasoning 思考区 / task 任务面板 / 指数退避重连) - 路径遍历防护:`safe_path_join` 限制访问范围在 `directory.root` 内 - Nuitka 编译:可打包为独立二进制 `fsys.bin`(`sys.frozen` 检测 + BASE_DIR 锚定) diff --git a/static/public/css/theme.css b/static/public/css/theme.css index 0e46c89..7b26f34 100644 --- a/static/public/css/theme.css +++ b/static/public/css/theme.css @@ -526,13 +526,18 @@ input[type="button"]:disabled { word-break: break-all; } .sp-image { display: block; max-width: 100%; border-radius: var(--radius-sm); } -.sp-run { margin-top: 0.5rem; } -.sp-run-frame { - width: 100%; - height: 120px; +.sp-run { + margin-top: 0.5rem; + max-height: 320px; + overflow: auto; + padding: 0.75rem; + background: var(--bg); border: 1px solid var(--border); border-radius: var(--radius-sm); - background: var(--bg); + font-size: 0.78rem; + line-height: 1.5; + white-space: pre-wrap; + word-break: break-all; } .sp-note { font-size: 0.82rem; color: var(--fg-secondary); margin-top: 0.4rem; } #sp-panel { margin-top: 0.4rem; } diff --git a/static/public/js/dnfs.js b/static/public/js/dnfs.js index 1daeb22..f4fc870 100644 --- a/static/public/js/dnfs.js +++ b/static/public/js/dnfs.js @@ -492,7 +492,9 @@ function renderSpLink(container, sp, appendNote) { appendNote(); } -/* JS 代码节点:审计(展开源码,textContent 防注入)+ 运行(插入到页面中 iframe 沙箱执行,捕获 console 输出) */ +/* JS 代码节点:审计(展开源码,textContent 防注入)+ 运行(在当前页面上下文执行,可直接操作页面加特效)。 + * 代码通过 new Function 注入包装 console 执行,可访问全局 document/window 对当前页面施加特效; + * console.log/error/warn 输出捕获到下方输出区。仅管理员可发布(后端强制)。 */ function renderSpJs(container, sp, appendNote) { var bar = document.createElement('div'); bar.className = 'sp-actions'; @@ -506,9 +508,9 @@ function renderSpJs(container, sp, appendNote) { pre.className = 'sp-code'; pre.style.display = 'none'; pre.textContent = sp.code; - var run_box = document.createElement('div'); - run_box.className = 'sp-run'; - run_box.style.display = 'none'; + var out = document.createElement('div'); + out.className = 'sp-run'; + out.style.display = 'none'; audit_btn.addEventListener('click', function(e) { e.stopPropagation(); var hidden = (pre.style.display === 'none'); @@ -517,52 +519,30 @@ function renderSpJs(container, sp, appendNote) { }); run_btn.addEventListener('click', function(e) { e.stopPropagation(); - if (run_box.firstChild) { // 已注入过,仅切换显示,不重复执行 - run_box.style.display = run_box.style.display === 'none' ? 'block' : 'none'; - return; + out.style.display = out.style.display === 'none' ? 'block' : 'none'; + if (out.textContent) return; // 已执行过,仅切换显示 + var lines = []; + var hook = { + log: function() { lines.push(Array.prototype.slice.call(arguments).join(' ')); }, + error: function() { lines.push('Error: ' + Array.prototype.slice.call(arguments).join(' ')); }, + warn: function() { lines.push('Warn: ' + Array.prototype.slice.call(arguments).join(' ')); } + }; + try { + /* 在当前页面上下文执行,函数体内可访问全局 document/window 施加特效 */ + new Function('console', sp.code)(hook); + } catch (err) { + lines.push('Error: ' + (err && err.message ? err.message : err)); } - run_box.style.display = 'block'; - var iframe = document.createElement('iframe'); - iframe.className = 'sp-run-frame'; // 高度由 iframe 内 postMessage 自适应 - iframe.setAttribute('sandbox', 'allow-scripts'); - /* 代码中若含 会提前闭合注入的 script 标签,先转义防逃逸 */ - var code = sp.code.replace(/<\/script/gi, '<\\/script'); - var html = '' - + '