Superpowers Visual Companion 认证加固从密钥 Bootstrap、同源 WebSocket 到 realpath 目录包含的完整实现【免费下载链接】superpowersAn agentic skills framework software development methodology that works.项目地址: https://gitcode.com/GitHub_Trending/su/superpowersSuperpowers 的 brainstorming 视觉伴侣Visual Companion是一个由 Node.js 内置模块实现的零依赖本地 HTTP WebSocket 服务用于在头脑风暴会话中向浏览器推送可交互屏幕并回传用户选择。本文基于仓库中的实施计划 2026-06-10-visual-companion-auth-hardening.md 展开完整讲解这次认证加固的威胁模型、十个 TDD 任务带密钥的根路径 Bootstrap、WebSocket 同源校验、sessionStorage重连凭证、安全响应头、/files/*realpath 包含、重启重连回归、Shell lint 与.gitignore治理、全量自动化验证与安全探针复测并结合 server.cjs 与 helper.js 的实际源码印证每项机制的落地方式适合需要为本地 Agent UI 设计密钥 Cookie 同源三层认证体系的开发者参考。加固目标、架构与技术栈该计划开篇明确了三项约束也是全文的判断标准目标Goal在不破坏可信同源屏幕 JavaScript和未来同源内嵌vendoredUI 库的前提下加固 brainstorming 视觉伴侣的认证与重连流程架构Architecture带密钥的根路径加载变为一个 Bootstrap 步骤——先设置 Cookie、把密钥存入标签页作用域的sessionStorage再导航到裸的/屏幕地址WebSocket 要求有效认证 浏览器同源Origin双重通过/files/*用 realpath 包含realpath containment阻止内容目录逃逸技术栈Tech StackNode.js 内置模块http、fs、path、crypto、零运行时依赖、仅测试使用的ws依赖、Bash 启停脚本、仓库自带的 Shell lint 脚本。计划还包含一条仓库级纪律执行期间除非明确要求不得提交——仓库自身的指令覆盖通用计划模板的提交节奏。配套的设计文档 2026-06-10-visual-companion-auth-hardening-design.md 给出了完整威胁模型值得先交代清楚因为它决定了每项修复的边界受保护资产伴侣提供的屏幕内容、会话密钥session key、Agent 将其作为用户反馈读取的state/events文件、伴侣会话目录下的本地文件范围内攻击者另一个localhost端口上的恶意浏览器标签页能向伴侣发请求但不应以伴侣 UI 身份认证的浏览器页面服务器绑定非回环地址时的直连远程客户端经由 URL 历史、Referrer 或误提交的本地状态造成的意外泄露指向内容目录之外符号链接或路径技巧范围外有意为之恶意的 Agent 生成的屏幕 HTML、恶意的同源内嵌 JavaScript。设计文档明确指出屏幕属于 Agent 的 UI 表面今天可以使用内联脚本未来可能使用 Alpine、Three.js 这类同源库若要防御恶意屏幕 HTML 需要更重的沙箱 iframe 架构不属于本次加固范围。设计文档同时列出了 PR 分支上被自动化与有头浏览器测试发现的五个现有失败跨源 localhost 页面可以借用 Cookie 打开 WebSocket 并向state/events写入攻击者选择/files/*会服务内容目录外的符号链接包括指向state/server-info、内含带密钥 URL 的链接会话密钥残留在屏幕页面可见 URL 中helper 用不带密钥的ws://host重连同端口重启后浏览器不再向重启的服务器出示 Cookie导致已打开的标签页卡在 tombstoneCompanion paused覆盖层直到手动刷新Shell lint 与生命周期测试需要清理以保证在 Codex 环境下稳定。文件地图一次加固改动了什么计划的 File Map 列出了全部改动面后续十个任务都落在这张地图内文件改动内容skills/brainstorming/scripts/server.cjs增加 Bootstrap 响应、共享安全响应头、WebSocketOrigin校验、/files/*realpath 包含skills/brainstorming/scripts/helper.js读取存储的会话密钥并拼接到 WebSocket URLtests/brainstorm-server/auth.test.jsBootstrap、响应头、同源 WS、跨源 WS、Cookie/文件认证回归tests/brainstorm-server/helper.test.js基于模拟浏览器的sessionStorageWS URL 覆盖tests/brainstorm-server/server.test.js/files/*符号链接包含回归tests/brainstorm-server/lifecycle.test.js让 start-server 超时参数测试强制后台模式补充重启重连凭证覆盖skills/brainstorming/scripts/start-server.sh、stop-server.sh修复 Shell lint仓库根.gitignore新增.superpowers/skills/brainstorming/visual-companion.md可选若行为变化需要面向操作者说明则补充 Bootstrap URL 剥离与可信同源屏幕 JS 的说明任务 1带密钥的根路径加载Bootstrap这是整个加固的核心GET /?keytoken不再是屏幕响应而是一个 Bootstrap 响应。计划要求在 auth.test.js 中先加两个 RED 测试带有效查询密钥的GET /应返回包含sessionStorage与location.replace的 Bootstrap 页而不是屏幕 HTML带有效 Cookie 的裸GET /则照常返回屏幕内容。计划的 RED 测试断言骨架现仓库中该文件保留了同名测试且实际实现比计划多了一层防御见下文实现与计划的差异一节await test(GET / with valid query returns bootstrap instead of screen content, async () { const res await get(/, { key: TOKEN }); assert.strictEqual(res.status, 200); assert(res.body.includes(sessionStorage), bootstrap should store the session key in tab storage); assert(res.body.includes(location.replace), bootstrap should navigate to the bare root URL); assert(!res.body.includes(Secret screen), bootstrap must not serve screen HTML at the keyed URL); }); await test(GET / with valid cookie serves the screen after bootstrap, async () { const res await get(/, { cookie: ${COOKIE_NAME}${TOKEN} }); assert.strictEqual(res.status, 200); assert(res.body.includes(Secret screen), cookie-authenticated bare root should serve the screen); assert(!res.body.includes(sessionStorage), bare screen response should not be the bootstrap page); });随后在 server.cjs 中实现最小 Bootstrap 响应。计划给出的实现与实际合入的 bootstrapPage 几乎一致实际版本给sessionStorage.setItem包了try/catch保证存储被浏览器拦截时仍然跳转function bootstrapPage(key) { const jsonKey JSON.stringify(String(key)); return !DOCTYPE html html headmeta charsetutf-8titleOpening Brainstorm Companion/title/head body script sessionStorage.setItem(brainstorm-session-key, ${jsonKey}); location.replace(/); /script /body /html; }配合一个从 URL 提取查询密钥的小工具并在handleRequest中于通过认证、设置 Cookie 之后、返回屏幕 HTML 之前拦截根路径上的有效查询密钥function queryKey(url) { const q url.indexOf(?); if (q 0) return null; return new URLSearchParams(url.slice(q 1)).get(key); } // handleRequest 内 const pathname pathnameOf(req.url); const keyFromQuery queryKey(req.url); if (req.method GET pathname / keyFromQuery timingSafeEqualStr(keyFromQuery, TOKEN)) { res.writeHead(200, securityHeaders({ Content-Type: text/html; charsetutf-8 })); res.end(bootstrapPage(keyFromQuery)); return; }实际实现见 server.cjs 的 handleRequest密钥比较使用timingSafeEqualStr内部为crypto.timingSafeEqual长度不等直接返回 false防止时序侧信道随后走 Bootstrap 分支。计划还特意标注若先实现任务 1、securityHeaders任务 4尚未引入可临时用res.writeHead(200, { Content-Type: text/html; charsetutf-8 })待任务 4 再替换——这体现了计划中任务间依赖的显式管理。为什么要用sessionStorage而不是别的机制设计文档的解释是helper 需要一个能扛过同端口重启、且不单纯依赖浏览器 Cookie 行为的重连凭证由于屏幕 HTML 本身就是可信的同源 UI把密钥存入标签页作用域存储在当前威胁模型下可以接受而且它实质上好过把密钥留在地址栏、历史与 Referrer 面上。sessionStorage的标签页作用域还意味着密钥不会跨标签页扩散。任务 2WebSocket Origin 强制校验这一步堵住的是计划威胁模型里最危险的攻击真实伴侣页面设置 Cookie 之后另一个 localhost 端口上的恶意标签页可以用同样的 Cookie 打开 WebSocket把attacker-injected之类的选择写进state/events等同于对在线 Agent 会话做提示注入。计划在 auth.test.js 中把wsConnect扩展为支持origin选项现仓库保留的实现见 auth.test.js#L59-L74并新增两条测试await test(WS upgrade with valid cookie and same-origin Origin opens, async () { const { outcome, ws } await wsConnect({ cookie: ${COOKIE_NAME}${TOKEN}, origin: http://localhost:${TEST_PORT} }); ws.close(); assert.strictEqual(outcome, opened); }); await test(WS upgrade with valid cookie but cross-origin Origin is rejected, async () { const eventsFile path.join(TEST_DIR, state, events); if (fs.existsSync(eventsFile)) fs.unlinkSync(eventsFile); const { outcome, ws } await wsConnect({ cookie: ${COOKIE_NAME}${TOKEN}, origin: http://localhost:9999 }); if (outcome opened) { ws.send(JSON.stringify({ type: choice, choice: attacker-injected, text: local attacker probe })); await sleep(300); } ws.close(); assert.strictEqual(outcome, rejected, cross-origin browser WS must not open even with cookie); assert(!fs.existsSync(eventsFile), cross-origin WS must not write state/events); });第二条测试值得细读它先删掉state/events再假设连接真的被打开并尝试注入事件最后断言连接被拒绝且state/events不存在——即使攻击者绕过断言路径也不会留下写入证据。这是攻击载荷 无副作用双重断言的写法。服务端实现在 server.cjsfunction isAllowedWebSocketOrigin(req) { const origin req.headers.origin; if (!origin) return true; // 非浏览器客户端仍然必须携带会话密钥 const host req.headers.host; if (!host) return false; return origin http:// host; }规则是请求携带Origin浏览器必带时必须严格等于http:// Host。攻击者页面形如Origin: http://localhost:9999/Host: localhost:58088的组合必然被拒合法页面Origin: http://localhost:58088/Host: localhost:58088在密钥或 Cookie 有效时通过无Origin的直接客户端脚本、测试用的ws库仍然允许但必须持有会话密钥。设计文档特别强调密钥是统一防线无论回环绑定、隧道还是远程绑定Host/Origin 白名单都挡不住 DNS rebinding而密钥可以。handleUpgrade 中的校验即计划要求的那一行function handleUpgrade(req, socket) { if (!isAuthorized(req) || !isAllowedWebSocketOrigin(req)) { socket.destroy(); return; } // ... 校验 Sec-WebSocket-Key 并执行 RFC 6455 握手 }注意这里直接socket.destroy()而不是回复 HTTP 错误码——对升级请求直接销毁连接是最简且不留握手痕迹的做法。认证侧的isAuthorizedserver.cjs#L341-L353则实现了查询密钥优先、否则回退 Cookie的逻辑且显式错误密钥不会回退到 Cookie——auth.test.js#L177-L180 专门断言了错误 key 有效 Cookie 仍返回 403防止攻击者用一个假 key 探测出 Cookie 仍然有效。任务 3helper 用存储密钥重连浏览器端的 helper.js 是注入到每个屏幕末尾的客户端脚本负责 WebSocket 连接、断线指数退避重连、状态胶囊Connecting / Connected / Reconnecting / Disconnected与 tombstone 覆盖层。加固前它连接的是不带密钥的ws://host完全依赖浏览器 Cookie——这正是同端口重启后标签页卡死在 tombstone 的根因。计划在 helper.test.js 中基于模拟浏览器先写 RED 测试。该测试文件构造了一个makeEnv()沙箱见 helper.test.js#L87-L132假的window.sessionStorage、假的WebSocket记录 URL 到sockets数组、假的定时器与可推进的时钟然后用new Function(...Object.keys(env), src)(...Object.values(env))在受控环境里执行 helper 源码真正驱动重连状态机而不是 grep 源码。计划要求的两条 URL 测试test(uses sessionStorage key in the WebSocket URL when present, () { const e makeEnv(); e.state.sessionKey stored-key-abc; e.boot(); assert.strictEqual(e.sockets[0].url, ws://localhost:7777/?keystored-key-abc); }); test(uses cookie-only WebSocket URL when no sessionStorage key is present, () { const e makeEnv(); e.state.sessionKey null; e.boot(); assert.strictEqual(e.sockets[0].url, ws://localhost:7777); });第二条是兼容性回退已经加载、只有 Cookie 而没有存储条目的旧页面仍然走原来的ws://host行为。生产实现即 helper.js#L25-L35function sessionKey() { try { return window.sessionStorage window.sessionStorage.getItem(brainstorm-session-key); } catch (e) {} return null; } function websocketUrl() { const key sessionKey(); return ws:// window.location.host (key ? /?key encodeURIComponent(key) : ); }helper.js#L77-L95 的connect()用new WebSocket(websocketUrl())建连另有reloadAfterRecovery()在 tombstone 状态下恢复连接时若存在存储密钥则location.replace(/?key ...)走一遍 Bootstrap 以刷新 Cookie否则退回location.reload()——这条恢复路径也被 helper.test.js#L174-L194 的模拟浏览器测试覆盖带密钥时断言发生的是replace(/?keystored-key-abc)而非裸reload。任务 4共享安全响应头计划要求给所有 HTML / 文件 / 403 / 404 响应统一加一组降低泄露 防框架化的保守响应头并且刻意不加script-srcCSP——因为伴侣现在注入内联 helper 脚本未来屏幕还可能加载同源内嵌库过严的 CSP 会破坏可信屏幕这一前提。RED 测试断言现仓库以assertSecurityHeaders辅助函数统一复用见 auth.test.js#L28-L34 与 L82-L86await test(HTML responses include leak-reduction and anti-framing headers, async () { const res await get(/, { key: TOKEN }); assert.strictEqual(res.headers[referrer-policy], no-referrer); assert.strictEqual(res.headers[cache-control], no-store); assert.strictEqual(res.headers[x-frame-options], DENY); assert.strictEqual(res.headers[content-security-policy], frame-ancestors none); assert.strictEqual(res.headers[cross-origin-resource-policy], same-origin); });403 响应同样必须带这套头。服务端实现是 server.cjs 的 securityHeadersfunction securityHeaders(headers {}) { return { Referrer-Policy: no-referrer, Cache-Control: no-store, X-Frame-Options: DENY, Content-Security-Policy: frame-ancestors none, Cross-Origin-Resource-Policy: same-origin, ...headers }; }...headers展开在末尾允许调用方附加Content-Type而不覆盖安全头。各响应的接入点handleRequest403 为res.writeHead(403, securityHeaders({ Content-Type: text/html; charsetutf-8 }))Bootstrap 与屏幕 HTML 用 200 同名头/files/*用securityHeaders({ Content-Type: contentType })其余 404 用securityHeaders()。各头的语义no-referrer确保从屏幕页面发出的任何外发请求不带 Referer密钥不会再经 Referer 面外泄no-store阻止屏幕内容进缓存X-Frame-Options: DENY与frame-ancestors none双保险防点击劫持Cross-Origin-Resource-Policy: same-origin防止本地其他源的页面把它当资源跨源抓取。另外auth.test.js#L208-L214 还断言了有效密钥加载设置的 Cookie 为HttpOnlySameSiteStrict对应 server.cjs#L398-L399 的Set-Cookie逻辑且 Cookie 名按实际绑定端口生成brainstorm-key-port见 onListen避免 EADDRINUSE 回退换端口后与另一个会话在共享的 localhost Cookie 罐里撞名。任务 5/files/*的 realpath 包含背景服务器在state/server-info里写入带密钥 URL 的启动信息权限 0600而/files/*曾直接readFileSync拼出来的路径。content/目录里若存在一个指向state/server-info的符号链接攻击者即可借合法认证通道读出密钥。RED 测试现仓库 server.test.js#L261-L269 保留了同名回归await test(does not serve symlinks that escape content dir via /files/, async () { const target path.join(STATE_DIR, server-info); const link path.join(CONTENT_DIR, linked-server-info.txt); try { fs.unlinkSync(link); } catch (e) {} fs.symlinkSync(target, link); const res await fetch(http://localhost:${TEST_PORT}/files/linked-server-info.txt); assert.strictEqual(res.status, 404, symlink to state/server-info must not be served); assert(!res.body.includes(server-started), response must not include server-info body); });包含判定逻辑计划版本 实际实现 isRegularFileInsideContentDirfunction isRegularFileInsideContentDir(filePath) { let stat, realContentDir, realFilePath; try { stat fs.lstatSync(filePath); if (stat.isSymbolicLink()) return false; if (!stat.isFile()) return false; if (stat.nlink ! 1) return false; // 实际实现额外增加的硬链接检查 realContentDir fs.realpathSync(CONTENT_DIR); realFilePath fs.realpathSync(filePath); } catch (e) { return false; } return realFilePath.startsWith(realContentDir path.sep); }要点lstat先于realpath——符号链接本身即被拒不存在解析后被放行的窗口realpath前缀比较用realContentDir path.sep结尾避免/content-evil被/content前缀误匹配任何stat/realpath异常一律返回 falsefail closed。/files/*的最终守卫server.cjs#L420-L433保持path.basename取文件名——嵌套路径依然不支持——并拒绝空名与点文件if (!fileName || fileName.startsWith(.) || !isRegularFileInsideContentDir(filePath)) { res.writeHead(404, securityHeaders()); res.end(Not found); return; }实现与计划的差异合入的实现比计划多了一条stat.nlink ! 1检查把硬链接也判为拒绝——对应的回归测试does not serve hard links to files outside content dir via /files/在 server.test.js#L273-L283。同一函数还被 getNewestScreen 复用根路径选择最新屏幕时同样跳过逃逸链接server.test.js#L284-L300 验证了根屏幕渲染不会被符号链接带出state/server-info。这是把一处包含函数变成两个端点共享的安全边界属于计划 File Map 之外的合理增量。任务 6重启重连回归同端口 同密钥这条回归验证端到端场景服务器杀掉后同目录重启、复用原端口用旧实例启动时的密钥连接新实例的 WebSocket 必须能打开——这正是浏览器标签页存了密钥、服务重启了时的真实路径。测试逻辑lifecycle.test.js#L281 起在fs.mkdtempSync(/tmp/bs-reconnect-)临时目录中分别指定BRAINSTORM_PORT_FILE/BRAINSTORM_TOKEN_FILE并设置BRAINSTORM_LIFECYCLE_CHECK_MS100000拉长看门狗以BRAINSTORM_DIRdir/s1启动实例 A从 stdout 的server-startedJSON 中取出带?key的 URL解析出keyA杀掉 A以BRAINSTORM_DIRdir/s2启动实例 B断言infoB.port infoA.port重启复用端口用ws://localhost:port/?keykeyA并显式带上同源Origin头发起 WebSocket 升级断言打开成功finally中关连接、杀进程、fs.rmSync清理临时目录。计划特意注明该测试在任务 2、3 落地后可能一开始就通过若属此种情况保留它作为覆盖但不称其为 RED——真实浏览器重连行为主要由任务 3 与最终人工/无头浏览器验证兜底。这个措辞体现了对测试先行与集成覆盖的区分。端口与密钥的持久化机制值得展开它解释了重启为什么能认出旧密钥server.cjs#L85-L151 中preferredPort()依次尝试BRAINSTORM_PORT环境变量、PORT_FILE记录的上次端口校验整数且落在 1023~65535、随机高端口49152~65534initialToken()依次尝试BRAINSTORM_TOKEN环境变量、TOKEN_FILE中的既有 token须匹配/^[0-9a-f]{32,}$/i读取后顺手chmod 0600、否则crypto.randomBytes(32)生成。onListen 只在拿到了首选端口时把端口与 token 写回文件——若因端口占用回退到随机端口就不写回避免覆盖共享文件、把另一个会话的已开标签页弄丢。密钥文件与server-info都以 owner-only 权限落盘。任务 7生命周期挂起修复与 Shell lint两个工程性修复保证npm test在 CICodex 环境下不挂起、lint 干净Shell lint计划先复现失败运行 scripts/lint-shell.sh 检查三个脚本预期输出SC2164: skills/brainstorming/scripts/start-server.sh line 128: cd $SCRIPT_DIR SC2034: skills/brainstorming/scripts/start-server.sh line 166: for i in {1..50} SC2034: skills/brainstorming/scripts/stop-server.sh line 57: for i in {1..20}最小修复cd $SCRIPT_DIR改为cd $SCRIPT_DIR || exit 1SC2164cd失败要显式处理两处未被读取的循环变量for i in {1..50}/for i in {1..20}改为for _ in ...SC2034未用变量。生命周期挂起lifecycle.test.js 中start-server.sh --idle-timeout-minutes的测试原本不传--background而 Codex 的CODEX_CI会让 start-server.sh 进入前台模式导致 execFileSync 一直等待。修复是让测试命令强制后台模式const out execFileSync(bash, [START, --project-dir, dir, --idle-timeout-minutes, 5, --background], { encoding: utf8 });验证命令为 lint 退出 0 且node lifecycle.test.js无挂起地退出 0。任务 8用.gitignore收纳持久化的伴侣状态使用--project-dir时伴侣会把状态写到项目下的.superpowers/目录含.last-token等持久化文件。计划用git check-ignore做了 RED→GREEN 的最小闭环# RED确认当前无匹配规则|| true 吞掉非零退出码 git check-ignore .superpowers/brainstorm/.last-token || true # 修复在 .gitignore 追加 .superpowers/ # GREEN应回显被忽略的路径 git check-ignore .superpowers/brainstorm/.last-token预期 GREEN 输出即.superpowers/brainstorm/.last-token。这一步防止含会话密钥的.last-token在真实项目目录里被顺手git add提交——密钥文件的安全0600 忽略规则在仓库内外各有一道。任务 9全量自动化验证无任何代码改动四层递进聚焦套件cd tests/brainstorm-server后依次node auth.test.js、node helper.test.js、node server.test.js、node lifecycle.test.js四条命令均须退出 0完整套件npm testtests/brainstorm-server/package.json 定义了测试入口要求 ws-protocol、helper、auth、server、lifecycle、stop-server 全部通过重复三次以暴露生命周期/文件监听类的偶发抖动for i in 1 2 3; do npm test || exit 1; done三次通过且不挂起Shell lintscripts/lint-shell.sh对 start-server.sh、stop-server.sh 及tests/brainstorm-server/stop-server.test.sh退出 0。任务 10安全探针复测与人工流程自动化全绿后复现此前发现问题的攻击探针。计划给出两条路径有暂存探针则直接node /tmp/.../probe-pr1720.cjs没有则在/tmp重建最小探针——用固定 token 启动伴侣、无头 Chrome 加载带密钥 URL、在另一个 localhost 端口起攻击者页面、尝试new WebSocket(ws://localhost:companion-port/)并发送{type:choice,choice:attacker-injected}、检查state/events。修复后的预期矩阵探针预期无密钥 / 错误密钥的 HTTP仍 403同源 helper 页面达到 Connected跨源 WebSocket打不开state/events不含attacker-injected指向server-info的符号链接404带密钥的浏览器加载最终停在裸/最后跑一遍人工/浏览器流程用--project-dir --open启动伴侣 → 推送一张屏幕 → 确认 URL 被剥离为/→ 状态胶囊到 Connected → 点击选项并核对state/events→ 停止并以同项目重启 → 确认已开标签页自动重连。预期全部步骤无需手动刷新 URL 通过。自审清单计划的收尾校验计划末尾的 Self-Review Checklist 是这份实施文档的方法论浓缩值得在采用同类流程时借鉴规格覆盖每条设计要求至少映射到一个任务本计划的 10 个任务与 设计文档 的 7 个设计点一一对应测试策略中的 12 条必须回归分散落在任务 1~7占位符扫描全文无未解决的占位标记或含糊的边缘情况步骤TDD 顺序每个生产改动任务都从聚焦失败测试或能展示当前失败的命令开始任务 6 是唯一例外且计划显式说明了它可能是出生即 GREEN的集成覆盖信任模型保留可信同源屏幕 JavaScript 与未来同源内嵌库的能力不加script-srcCSP、不加 iframe 沙箱——后者被明确列入 Deferred Work不提交规则执行过程不 commit。小结这套加固的可复用要点从 server.cjs 的最终形态看这次加固沉淀出一组本地 Agent UI 可直接借鉴的模式密钥是统一身份?key查询、Cookie、sessionStorage三处承载同一个 32 字节随机 token比较一律timingSafeEqual任何一处失效都能被其余两处补偿密钥扛重启、Cookie 扛子资源、存储扛浏览器 Cookie 行为退化Bootstrap 剥离 URL带密钥 URL 只作为一次性入口location.replace(/)让可见地址、历史与 Referrer 面不再含密钥双重门禁而非单一白名单WebSocket 认证且同源Origin文件服务 认证且realpath 包含 lstat/nlink检查认证绕过和路径绕过被解耦成两个独立防线保守头、严边界只加不破坏内联脚本前提的防泄露/防框架化头把恶意屏幕 HTML这类更大问题显式推到独立的沙箱架构议题测试即证据每条安全性质跨源注入、符号链接逃逸、重启重连、URL 剥离都有对应自动化回归且攻击者测试带无副作用断言。读者若要在自己的仓库中审计类似机制建议从 tests/brainstorm-server 目录读起auth.test.js 是门禁矩阵、helper.test.js 是模拟浏览器驱动的重连状态机、server.test.js 是文件包含回归、lifecycle.test.js 是跨重启的集成证据四个文件合起来就是一次完整的安全验收。【免费下载链接】superpowersAn agentic skills framework software development methodology that works.项目地址: https://gitcode.com/GitHub_Trending/su/superpowers创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考