## 品牌标识(本次会话) 起因:品牌此前没有任何图形标识 —— 唯一 favicon 是内联 data-URI 里的字母「A」, 那是 v2.0.0「Aurora Admin → Kole UI」改名漏掉的一处(PC 顶栏也是「A」, 移动端站已是「K」;移动端文档站则完全没有 favicon)。 - 几何:24 网格三个互不接触的笔画(竖 + 两斜),圆头描边; 描边 2.25 → 16px 标签页尺寸下正好 1.5px = 规范原文「描边1.5px」 - 取色分两套(刻意):favicon 硬编码品牌蓝/白(渲染在浏览器标签栏,不继承 kole-dark); 顶栏标记走 currentColor(实测暗色下自动转 rgb(20,22,28)) - 新增 theme-color 双条(light #FFFFFF / dark #1C1F26,取 --kole-color-card-bg) - 修 site/app.js hero 标语 KOLE ADMIN → KOLE UI(改名变形残留) - 移动端 7 个模板补 favicon(此前计数 0) 验收:门禁 9 条全 OK(site-routing/site-routes/mobile-docs/mobile-site/isolation/ theme/nav/i18n/icons);PC 回归 1464/1464 · 移动端 807/807,各连跑 8 次一致; 两端 favicon 405 字节逐字节一致;PC 站控制台错误 1→0。 ## 并行会话成果(本次一并入库) - 图标系统:2576 图标(TDesign/Element Plus,MIT)+ 11 端注入 + 5 个构建门禁工具 + IconPreview 预览页 + ICON-SPEC.md 冻结规格 - 移动端平台:47 组件 × 6 端 + 文档站 53 页 + 隔离门禁 - PC 组件:103 个大后台组件 / 组件11 批次 - uni-app:PC 端试点 + 移动端端实现 + 真实编译验证 ## 工程 - .gitignore 补 .scratch/ 与 .zcode-preexisting-*.txt(会话中间产物,实测 9.1MB,不入库) - CHANGELOG 补品牌标识条目 - ROADMAP 登 S8-P4(品牌标识任务包 + og:image/apple-touch-icon 未做部分)
21 KiB
安全审计修复说明与待决事项
生成时间:2026-09-19 | 审计对象:本仓库 +
192.168.5.7上运行的kole-ui-showcase容器 本文档分两部分:已修复(我直接做了,含证据)与 待你拍板(我做不了主的,逐项给选项与建议)。
一页速览
| 状态 | 数量 | 说明 |
|---|---|---|
| 已在仓库修复并验证 | 6 项 | 见第二节,全部有 exit 0 证据 |
| 已部署并在线上验收 | 全部 | 2026-09-19 重建容器:6/6 断言 + 396/396 URL + 响应头全项通过,见第三节 |
| 仍待你拍板 | 4 项 | D3 / D4 / D6 / D8(D1、D2、D5、D7、D9、D10 已决,见第四节开头) |
| 过程异常 | 4 项 | 见第五节 E1–E4(你已确认无其它会话,属当时并发写入) |
一句话:洞已堵上且已上线——线上访问 specs/组件1.txt 现在返回 404,站点 396 条 URL 全部 200。
二、已修复(仓库层,全部已实测验证)
1. 规范原文泄漏的根因(.dockerignore)
问题:.dockerignore 里 *.md / *.ps1 / *.yml / *.png 这类写法,在 Docker 下只匹配构建上下文的根目录。实测(真实 docker build):
.dockerignore: *.md
COPY . /out/ -> /out/sub/nested.md ← 嵌套文件照样进镜像
所以 .design_library/kole-ui/SKILL.md、嵌套 README.md 等一直会被打进镜像;而部署目录 /opt/kole-ui 干脆没有 .dockerignore,于是 specs/、agent-reports/、preview/、ui_kits/ 全部随 COPY .design_library/ 进了镜像并对外可下载。
修复:glob 全部加 **/ 前缀,保留显式目录排除与 !README.md(只放行根级 README)。
验证(我在服务器上用当前文件做真实构建):
应排除 -> specs/ 0 | agent-reports/ 0 | preview/ 0 | ui_kits/ 0 | nested.md 0 | AGENTS.md 0 | docker-compose.yml 0
应保留 -> site/index.html 1 | site/data.json 1 | site/tokens/tokens.css 1 | site/sources/x.txt 1
frameworks/*.css/.vue3 1 | tests/index.html 1 | tests/report.json 1
.design_library/kole-ui/components/button.json 1 | README.md 1
BUILD_EXIT=0
2. nginx.conf 加固
- 补
absolute_redirect off;—— 修复根路径 302 丢失宿主端口(Location: http://192.168.5.7/site/,而该机 :80 无服务 → 根地址直接打不开)。 - 补
server_tokens off;—— 不再暴露nginx/1.31.3。 - 补 CSP +
X-Content-Type-Options+Referrer-Policy+Permissions-Policy+X-Frame-Options,CSP 与site/dev-server.js里的SECURITY_HEADERS逐字一致(本地长期验证可用的组合,避免过严导致演示页白屏)。 /healthz改用default_type,消除重复的Content-Type(原先是return 200的默认类型与add_header叠加)。
验证:子 agent 在真容器里跑 nginx -t → syntax is ok / test is successful / exit 0;并做了修复前/修复后运行时对照(Location 由绝对变相对、Server 由 nginx/1.31.3 变 nginx、/healthz 由两条 Content-Type 变一条且不丢安全头、gzip 行为等价)。
3. Dockerfile
补 COPY sitemap.xml /usr/share/nginx/html/sitemap.xml(此前生产 /sitemap.xml → 404)。
未动基础镜像 tag、未加 USER(nginx 官方镜像 master 需 root 绑 80,擅自改会让容器起不来)——运行期加固见 D6。
4. site/dev-server.js:单请求崩溃
问题:GET /site/%00 → decodeURIComponent 解出 NUL → fs.readFile 同步抛 ERR_INVALID_ARG_VALUE → 异常逃逸出请求回调 → 整个进程退出(畸形百分号编码反而处理正确)。CI 与本地回归都跑这个服务。
修复:编码形态拦截补 00/2500、解码后再做一次 NUL 校验(双保险)、readFile try/catch。未使用 uncaughtException 兜底(那是掩盖)。行为不变:92 条合法路径 status / content-type / 字节级一致。
验证:node tools/verify-dev-server.mjs → 20 checks / EXIT=0,含 3 条「修复前必失败」的反例断言(进程存活、突发后仍 200、stderr 无崩溃栈)。
5. 消除动态求值 sink(运行期 + 构建期)
问题:site/app.js 与 tools/precompute.mjs 都把 defineEmits([...]) 的数组字面量交给函数构造器求值。实测:
输入 defineEmits([1, globalThis.__pwned = "executed-arbitrary-js"])
旧实现 -> 真的执行了
即:任何能改 frameworks/*.vue* 的人(投毒 PR / 内部威胁)在组件详情页被打开时即可执行同源 JS;构建期同源版本在构建机上执行,而 CI 往往带 secrets。
修复:改成只解析字符串字面量的 parseStringLiteralArray——非字符串元素 / 未识别转义 / 裸换行 / 结构不符一律整体拒绝返回 null(跳过抽取、不报错、绝不执行)。构建期实现独立成 tools/lib/parse-string-literal-array.mjs。
等价性验证:node tools/verify-emits-parse.mjs → 17 checks / EXIT=0,158 个 frameworks/*.vue 双实现抽取结果不一致 0(54 处字面量 / 96 条事件名),且新实现对 6 类对抗输入全部拒绝、不执行。
6. 新增验证脚本与变更记录
tools/verify-dev-server.mjs(20 checks)、tools/verify-emits-parse.mjs(17 checks)——均为零依赖、exit 1表示失败、含「修复前会失败」的反例断言。CHANGELOG.md追加[Unreleased] → Security · 生产部署链审计修复(2026-09-19)。- ⚠️ 规划修正已在 CHANGELOG 标注:
PLAN.md§8「不改site/dev-server.js(已工作良好)」的立论被实测推翻,按 AGENTS.md 第五节「以实测为准」处理。
回归证据
连跑 9 次(子 agent):每次 passRate 100% | pages 79 (all-pass 79) | 1017/1017 | N/A 34 | rc=0
主 agent 独立复跑 1 次(独立端口 13411):passRate 100% | 1017/1017 | N/A 34 | EXIT=0
零依赖铁律:zero-dep OK(EXIT=0)
三、已部署(2026-09-19)
按 AGENTS §九 的标准流程部署(备份 → 暂存目录核对 → 替换 → docker compose build && up -d)。共执行两次:
- 修复落地:把审计修复 + 工作树当前状态推上线(消除 288 文件漂移)。
- 重新部署:
npm run build:site两步构建重生成data.js/data.json/site/sources/site/details后,再跑一次打包流程保持线上与仓库一致(实测线上/site/data.js102,555B、/site/data.json1,251,099B,与仓库一致;data.json仍为完整版,符合铁律 5)。
回滚点:
/opt/kole-ui.pre-audit-20260919-0609—— 审计前的原始状态(含被泄漏的specs/、旧nginx.conf.bak)/opt/kole-ui.prev-20260919-0614—— 第一次修复部署后的状态/root/kole-ui-backup-20260919-0609.tgz(877 KB)、/root/kole-ui-backup-20260919-0614.tgz(882 KB)
| 项目 | 修复前(实测) | 修复后(实测) |
|---|---|---|
/.design_library/kole-ui/specs/组件1.txt |
200 / 8623B(规范原文可下载) | 404 |
/.design_library/kole-ui/agent-reports/*.json |
200 | 404 |
嵌套 SKILL.md / README.md |
200 | 404 |
/AGENTS.md、/package.json、/Dockerfile、/nginx.conf |
404 | 404(一直未泄漏) |
/sitemap.xml |
404 | 200 |
tests/report.json(首页通过率角标数据源) |
404 | 200 |
| 根路径 302 | Location: http://192.168.5.7/site/(丢端口,落点无服务) |
Location: /site/(相对,保留端口) |
| 响应头 | 无安全头,Server: nginx/1.31.3 |
CSP + nosniff + Referrer-Policy + Permissions-Policy + X-Frame-Options,Server: nginx |
/healthz |
两条 Content-Type |
1 条,且安全头未丢 |
/site/dev-server.js |
旧版(无白名单/无敏感路径过滤) | 加固版(isPublicPath + KOLE_PORT) |
| 组件文件 | 09-06 快照,与仓库漂移 288 个文件 | 与工作树一致 |
| sitemap 的 396 条 URL | 396/396 → 200 | 396/396 → 200(无回归) |
| 容器 | healthy | healthy |
对 Agent 承诺的资源(.design_library/kole-ui/{metadata.json,components/index.json,css.json,colors_and_type.css})仍正常 200 —— 排除的只是规范原文、agent-reports、preview、ui_kits 这些不该外发的部分。
访问方式:按你的决定保持 127.0.0.1:3311 回环。经实测确认,服务器上没有任何 frp 隧道 / 反代 / 域名指向该服务,所以从任何其它机器都打不开是必然的——唯一的访问路径是 SSH 隧道:
ssh -o ExitOnForwardFailure=yes -N -L 13311:127.0.0.1:3311 root@192.168.5.7
# 浏览器打开 http://127.0.0.1:13311/site/ (本机 13311 是为了不占本地 dev-server 的 3311)
已实测:经隧道对线上部署跑完整文档站冒烟(真实浏览器)全部通过——路由、中英切换、主题面板、详情页渲染均正常。
若想让局域网内其它设备也能直接打开 http://192.168.5.7:3311/,需把 compose 绑定改为 0.0.0.0:3311 并注意 Docker 会绕过 ufw —— 这属于显式暴露变更,等你说一句我就改。
四、待你拍板(D1–D10)
2026-09-19 决定记录(依据你的指示「暂时不考虑线上公用,但是还是得部署,修改原本的设计和计划」):
项 结论 落地位置 D1 是否部署 ✅ 已部署(按流程:备份 → 暂存核对 → 替换 → 重建;6/6 断言 + 396/396 URL 通过) 本文第三节、CHANGELOG Deploy段D2 部署方式 ✅ 已改为可复现:新增 tools/pack-deploy.mjs(.dockerignore为唯一真源 + 硬断言),流程写进 AGENTS §九tools/pack-deploy.mjs、AGENTS.md §九、ROADMAP S5-P13D5 端口暴露 ✅ 保持回环、不对外公用;AGENTS §九 明确「打不开是正常的,用 SSH 隧道」 AGENTS.md §九D7 PLAN §8 冻结条款 ✅ 已按实测修正(§7 与 §8 两处都标注 ⚠️ 规划修正) PLAN.md、CHANGELOG.mdD9 测试基线口径 ✅ 已同步为 1017 通过 / 0 失败 / 34 N/A / 共 1051(标注口径来源) TESTING.md、AGENTS.md §八、ROADMAP 规划修正记录D10 是否还有其它会话 ✅ 你已确认无;本次审计中观察到的是当时并行写入,后续已复核无冲突 本文第五节 E1–E4 仍未决:D3(
tests/report.json是否随镜像发布)、D4(正式域名)、D6(容器运行期加固)、D8(契约对外承诺口径)。这四项已登记为 ROADMAP 任务包 S5-P14/P15/P16,等你有空再定。
D1|是否现在重建并重新部署线上容器?【最高优先】
- 选项 A(建议):只同步「本次修复的文件集」重建,不动组件文件。范围小、可回滚、立即堵住规范原文泄漏与掉端口问题。
- 选项 B:把当前工作树整体同步(顺带消除 288 文件的漂移)。代价:会把仓库里 286 个未提交的组件改动一起上线(这些改动本地回归 100%,但等于一次完整发布)。
- 选项 C:暂不部署,只保留仓库修复。
我的建议:先做 A(堵漏),B 作为一次独立「发布」动作、等工作树改动提交审阅后再做。
选 A 时的执行序列(等你一句话我就执行):
# 1) 备份现状(在 192.168.5.7 上)
cd /opt/kole-ui && tar -czf /root/kole-ui-$(date +%Y%m%d-%H%M).tgz .
# 2) 只更新 5 个文件:nginx.conf / Dockerfile / .dockerignore / site/dev-server.js / site/app.js
# (加上新增的 tools/lib、tools/verify-*.mjs 可选)
# 3) 重建并重启
docker compose build && docker compose up -d
# 4) 验收断言(四条必须全中)
curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:3311/.design_library/kole-ui/specs/组件1.txt # 期望 404
curl -sI http://127.0.0.1:3311/ | grep -i '^Location' # 期望 /site/
curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:3311/sitemap.xml # 期望 200
curl -sI http://127.0.0.1:3311/healthz | grep -ci '^content-type' # 期望 1
D2|部署方式:怎么根治「手工拷贝导致漂移」
这就是这次事故的根因:/opt/kole-ui 是手工拷贝的目录,无 git、无 .dockerignore,所以
① 排除规则失效导致规范泄漏;② 曾经修好的 absolute_redirect off; 被下一次拷贝覆盖(它只存在于 nginx.conf.bak-20260916,从未进过仓库 git 历史)。
- 选项 A(建议):改用 rsync/脚本按固定排除清单同步 + 重建,排除清单与
.dockerignore保持同一份真源。 - 选项 B:服务器上
git clone仓库 +docker compose build,用 git 版本作为唯一真源。 - 选项 C:保持现状手工拷贝(不推荐,同样的回退会再来一次)。
D3|tests/report.json 要不要随镜像发布
- 现状:当前
.dockerignore保留它 → 重建后首页「通过率」角标会在线上显示(子 agent 一度排除,被我方流程外的改动改回保留,并在文件里写了理由)。 - 选项 A(建议):保留 —— 首页那个「79 测试页 + 通过率」卡片本来就在页面上,显示数字才算兑现。
- 选项 B:排除 —— 镜像更干净,但卡片静默不显示(当前线上就是 404 → 静默隐藏)。
- 若选 B,删掉
.dockerignore中相关保留注释对应的行即可回滚。
D4|sitemap 与包的正式域名(SITE_URL_BASE)
sitemap.xml 内容目前是 https://YOUR-ACCOUNT.github.io/kole-ui/…(占位域名),package.json 的 repository / homepage 同样是占位。
- 需要你给:正式域名(或明确「不做 SEO,接受占位」)。
build-site.ps1已支持通过环境变量SITE_URL_BASE覆盖;tools/release.mjs已有占位符闸门会拦住正式发布。- 顺带一提:
tools/verify-site-routing.mjs目前断言 sitemap 里必须含占位 URL——如果换了真域名,这个断言要同步改(否则校验会失败)。
D5|端口暴露策略
- 现状:绑定
127.0.0.1:3311,只有本机/SSH 隧道能访问(我从局域网直连192.168.5.7:3311超时,符合预期)。 - 选项 A(建议):保持回环,并在 README/交接文档写明「必须隧道访问」,避免有人以为服务挂了。
- 选项 B:挂 1Panel 反代 + 域名(要不要加 Basic Auth / IP 白名单?)。
- 影响面:选 B 会让规范原文泄漏面从「本机」变成「公网」,必须先把 D1 做完。
D6|容器运行期加固(需要一次重启窗口)
read_only: true + tmpfs、cap_drop: [ALL]、security_opt: no-new-privileges、基础镜像 digest 固定。
好处明确,但改 compose 后必须重建容器,且 read_only 配错会让 nginx 起不来(需要临时可写 /var/cache/nginx、/var/run)。要不要做、什么时候做,你定。
D7|PLAN.md §8 冻结条款的修正
PLAN.md §8 写着 ❌ 不改 site/dev-server.js(已工作良好),§7 有对应验收条目。实测推翻了「已工作良好」,我已按 AGENTS.md 路径修了代码并在 CHANGELOG 标注 ⚠️ 规划修正,但没有改 PLAN.md 本身(它在冻结清单里)。
需要你确认:是否把该行改成「dev-server 的畸形路径处理属必修项」。
D8|契约 JSON 的对外承诺怎么统一
- 本地:79 份契约 +
index.json。 - 线上:只有
button/card/input/modal/select/table.json+index.json(7 个文件),tag.json、calendar.json等 → 404。 - 文档三处互相矛盾:
site/llms.txt写「契约覆盖全部 79 个组件」;AGENTS.md写「契约 JSON 79」;而index.json自己的 description 写「其中 6 个核心组件另含契约 JSON」。
选项:A 全量发布 79 份(镜像略大,但对 Agent 消费的承诺为真);B 只发布核心 6 份,并把 llms.txt/AGENTS.md 的表述统一改为「6 份核心契约 + 79 组件索引」;C 保持现状(不推荐,等于承诺失实)。
D9|测试基线数字的口径同步
TESTING.md 记的是 1009 / 35,HEAD 提交信息也写「回归1009-1009」,而当前工作树实测是 1017 通过 / 34 N/A。差异来自工作树里已有的未提交改动,不是本次修复造成。建议在合并这批改动后统一同步(我没改 TESTING.md,它不在允许清单内)。
D10|是否还有别的会话/自动化在动这个仓库
见第五节 E1–E3。如果有,建议先停掉再合并,否则两边互相覆盖。这是本次最需要你确认的运维问题。
五、必须让你知情的过程异常
E1|.dockerignore 在我方子 agent 交付后被第三方进程改写。
子 agent A 交付的哈希是 3b34ffa6(其中排除了 tests/report.json),磁盘现状是 843cb650(保留 report.json 并写了理由注释)。功能上现状更优(首页通过率卡片能显示),但 A 报告的 47 条断言不覆盖现状版本——所以我用当前文件独立重做了真实构建复验(第二节 1,全过)。这也意味着我已无法声称「这个文件的每处改动都来自本次派活」。
E2|tools/precompute.mjs(05:56:41)与 tools/lib/parse-string-literal-array.mjs(05:56:45)不在任何子 agent 的允许清单内,却被写入了;且 05:56:29 有一次我没派出的构建运行。
那次运行重写了 494 个文件(site/sources/** 395、site/details/* 79、site/data.js、site/data.json、site/changelog.json),但 sitemap.xml 与 site/tokens/* 未变——即只跑了第二步 node tools/precompute.mjs,没跑 build-site.ps1。
我复核了内容与后果:
tools/precompute.mjs的node --check通过、tools/lib/...mjs可加载、与site/app.js在 158 个.vue上抽取结果一致、对恶意输入不执行;- 重建后的
site/data.js哈希5fdffe24…与构建前、与线上部署三方完全一致 → 这次构建是语义无损的,同时独立证明了新的安全解析器不改变构建输出(不依赖我方验证脚本的口径); - 漂移比对从我审计时的「288 个文件不同」变为「289」,差额恰好是本次修复的
site/app.js,其余无新增漂移。 结论:改动立意是对的(构建期确实有同一个求值 sink,打穿的是构建机而非浏览器),但来源不明、未走派活流程。若这是你的另一个会话在做同一件事,请注意两边会互相覆盖(见 D10)。
E3|共享模块头部引用的 tools/verify-precompute-emits.mjs 并不存在。
且注释里描述的「源码逐行等价 + 5000 例种子随机等价」比实际存在的检查更强。我已把注释改为描述真实存在的门(tools/verify-emits-parse.mjs:158 文件结果等价 + 对抗语料),改动后复跑该脚本仍 17 checks / EXIT=0。
若你确实想要那道更强的门(源码等价 + 随机 fuzz),需要另派一个任务实现。
E4|服务器 /tmp 残留 kole-di-final*(非本次产物)。
我清理了自己创建的全部临时目录与 scratch 镜像;这三项来源不明,未删以免干扰可能仍在运行的会话。生产容器 kole-ui-showcase 全程未动(Up 2 days (healthy))。
六、审计中被验证为「无问题」的部分(免得重复怀疑)
| 检查项 | 结果 |
|---|---|
路径穿越(../、..%2f、%2e%2e、....//、反斜杠、.git/config) |
全部 403 |
搜索框 XSS(<img src=x onerror=…>) |
0 个注入节点、0 次执行 |
| 路由滥用 | 无效 slug / 越界路径全部回落首页,无异常 |
| 代码高亮渲染 | 先全量转义再还原 token,无注入 |
| 硬编码凭据 | 未发现(命中的都是 design-token 变量名) |
| 零运行时依赖铁律 | 成立(dependencies 为空) |
| sitemap 里的 396 条 URL | 线上 396/396 → 200(静态页无死链;sitemap 文件本身 404 另见第二节 3) |
| 容器健康 | healthy,/healthz 200 |
附录:本次审计与修复用到的复验命令
# 全量回归(需先起服务;服务端口可用 KOLE_PORT 覆盖)
KOLE_PORT=13411 node site/dev-server.js &
REG_BASE=http://127.0.0.1:13411 node tools/run-regression.mjs
# 两处缺陷的定点验证
node tools/verify-dev-server.mjs # 20 checks,含 3 条反例断言
node tools/verify-emits-parse.mjs # 17 checks,158 文件双实现等价
# 铁律
node -e "const p=require('./package.json');const d=Object.keys(p.dependencies||{});if(d.length){console.error('FAIL');process.exit(1)}console.log('zero-dep OK')"
# 部署配置复验(在 192.168.5.7 上,用一次性容器,不碰生产)
docker run --rm -v /tmp/<你的>/default.conf:/etc/nginx/conf.d/default.conf:ro nginx:alpine nginx -t
证据留档:C:\Users\12914\Desktop\组件规范第一套\.zcode\deployed-manifest.txt(线上 1394 文件哈希清单)、.zcode\drift-check.mjs(漂移比对脚本)、.zcode\sitemap-check.txt(396 条 URL 清单)。