devforai-creator/Safe-UGC-UI

GitHub: devforai-creator/Safe-UGC-UI

一套安全聚焦的 JSON UI 卡片 DSL、验证器和 React 渲染框架,用于安全地描述、验证和渲染不受信任的用户或 LLM 生成内容。

Stars: 0 | Forks: 0

# 安全的 UGC UI Safe UGC UI 是一个 pnpm workspace,用于描述、验证和渲染不受信任的 UI 卡片。 它结合了 JSON 卡片格式、基于 Zod 的类型、JSON Schema 生成、一个专注于安全的 validator,以及一个将用户提供的 UI 限制在受限 container 内的 React renderer。 ## 状态 - Phase 2 以及 `v1.0` 交互式 container 里程碑已实现。 - 当前发布的包版本为 `3.0.1`:`@safe-ugc-ui/types`、`@safe-ugc-ui/schema`、`@safe-ugc-ui/validator`、`@safe-ugc-ui/react`。 - `v1.1` 安全视觉/布局包已在 `main` 分支上实现。 - 样式系统包括字体族 token、文本阴影、重复线性渐变、`aspectRatio`、`backdropBlur`、结构化 `clipPath` 以及节点级别的 `responsive.medium` / `responsive.compact` 覆盖。 - 节点支持 `$if` 条件渲染、结构化 `Switch` 分支,并且 `Button` / `Toggle` 支持 `disabled`。 - 文本创作支持结构化 `$template`、`Text.spans` 以及 `Text.maxLines` / `truncate`。 - 卡片支持顶层 `fragments` 和 `$use` 引用,用于非递归子树重用。 - `Accordion` 和 `Tabs` 作为 renderer 拥有的交互式 container 实现。 - `packages/demo` 是用于本地开发的私有 playground 应用。 ## 包 | 包 | 用途 | | ------------------------ | ------------------------------------------------------------------------------ | | `@safe-ugc-ui/types` | Zod schema、推断的 TypeScript 类型、常量和共享的 ref-path 辅助工具 | | `@safe-ugc-ui/schema` | JSON Schema 生成和构建好的 `ugc-card.schema.json` 产物 | | `@safe-ugc-ui/validator` | 结构、样式、安全、限制验证以及安全卡片加载 | | `@safe-ugc-ui/react` | `UGCRenderer`、`UGCContainer`、renderer 内部实现、资源/样式辅助工具 | | `@safe-ugc-ui/demo` | 基于 Vite 的 playground,用于编辑卡片 JSON 和预览输出 | ### 包边界策略 - 支持 Semver 的公共入口点是包的根导出和 `@safe-ugc-ui/schema/ugc-card.schema.json`。 - `@safe-ugc-ui/types/internal/*` 保留导出用于 workspace 协调和高级工具,但不受 semver 稳定性承诺的约束。 - 外部使用者应避免使用 `@safe-ugc-ui/types/internal/*`,除非他们准备好固定确切的版本并吸收内部重构。 ### 依赖图 ``` types ─────┬── schema ├── validator └── react ──── validator │ demo ────┬── react └── validator ``` ## 安装 只需安装您需要的包: ``` pnpm add @safe-ugc-ui/react pnpm add @safe-ugc-ui/validator pnpm add @safe-ugc-ui/schema pnpm add @safe-ugc-ui/types ``` `@safe-ugc-ui/react` 已经依赖于 `@safe-ugc-ui/types` 和 `@safe-ugc-ui/validator`。 ## Workspace 命令 - `pnpm build` — 构建所有 workspace 包 - `pnpm test` — 在 workspace 模式下运行 Vitest - `pnpm test:clean-checkout` — 从没有预构建包 `dist` 输出的干净 workspace 中,验证 workspace 测试以及 demo 类型检查和构建 - `pnpm test:contracts` — 针对面向宿主的 validator/renderer 边界运行定向的契约回归门控,包括一个干净的 workspace canary - `pnpm test:contracts:packages` — 仅运行 validator/react 契约回归测试套件,不带 workspace canary - `pnpm test:run` — 一次性运行完整的 workspace 测试套件 - `pnpm test:coverage` — 运行带覆盖率的 workspace 套件 - `pnpm release:pack-check` — 验证每个可发布的 tarball 是否包含预期的构建输出和导出的入口点 - `pnpm release:check` — 运行共享的预标签发布基线:格式检查、契约门控、clean-checkout 门控、构建、tarball/导出验证、类型检查、审计和覆盖率 - `pnpm clean` — 删除包的 `dist` 目录 - `pnpm format` — 使用 Prettier 格式化 workspace - `pnpm format:check` — 检查 workspace 是否已使用 Prettier 格式化 主要的 CI 工作流和基于标签的发布工作流都使用 `pnpm release:check` 作为共享的 发布基线。这两个工作流都在文档记载的 Node `24` 和 pnpm `11.17.0` 工具 基线上运行它;`publish.yml` 也使用 Node `24` 进行 npm trusted publishing。 ## 快速开始 ### 安全地加载卡片 当输入仍然是 JSON 字符串时,请使用 `loadCardRaw()`,这样 validator 就可以在解析前拒绝过大的 payload,运行完整的验证流水线,并且仅在成功时返回类型化的卡片: ``` import { loadCardRaw } from '@safe-ugc-ui/validator'; const rawCard = `{ "meta": { "name": "hello", "version": "1.0.0" }, "state": { "greeting": "Hello, World!" }, "views": { "Main": { "type": "Text", "content": { "$ref": "$greeting" } } } }`; const result = loadCardRaw(rawCard); if (!result.valid) { console.error(result.errors); } else { console.log(result.card.views.Main); } ``` 如果卡片已经被解析,请改用 `loadCard()`。 当您只需要诊断信息而不需要返回类型化的 `UGCCard` 时,请使用 `validateRaw()` 或 `validate()`。 对于大多数 validator 错误,`ValidationError.path` 指向确切失败的字段。对于某些 来自嵌套 Zod union 的结构性 `SCHEMA_ERROR`,`path` 指向最近的稳定 祖先节点,而 `message` 包含最多三个更深层的子级位置。 ### 在 React 中渲染卡片 建议在宿主摄取时使用 `loadCardRaw()` 或 `loadCard()` 进行验证。`UGCRenderer` 在渲染前仍然会进行验证,并重新验证有效合并的 runtime 状态,因此即使宿主传递了解析后的卡片对象,渲染边界也会保持防御性。 ``` import { UGCRenderer } from '@safe-ugc-ui/react'; import { loadCardRaw } from '@safe-ugc-ui/validator'; export function CardPreview({ rawCard }: { rawCard: string }) { const result = loadCardRaw(rawCard); if (!result.valid) { console.error(result.errors); return null; } return ( { console.error(errors); }} /> ); } ``` 关键的 renderer props: - `viewName` 用于渲染特定的命名视图;无效名称会通过 `onError` 发出 `RUNTIME_VIEW_NOT_FOUND` 信号并且不渲染任何内容 - `assets` 用于将 `@assets/...` 引用解析为宿主控制的 URL;宿主拥有最终的 URL 来源和任何来源允许列表策略 - `state` 用于覆盖或扩展 `card.state`;合并后的状态在渲染前会被重新验证 - `containerStyle` 用于设置外部隔离 container 的样式,而不替换受保护的隔离属性 - `hostOverflow` 仅覆盖受保护的隔离属性中的 `overflow` 键 - `iconResolver` 用于将图标名称映射到 React 节点;如果省略,`Icon` 节点会软跳过并通过 `onError` 发出 `RUNTIME_ICON_RESOLVER_MISSING` 信号 - `onAction` 用于接收 Button 和 Toggle 操作事件 - `onError` 接收结构化的 `RendererError[]` 诊断信息,用于验证失败和 runtime renderer 问题 ### 生成 JSON Schema 用于编辑器集成或外部结构验证: ``` import { generateCardSchema } from '@safe-ugc-ui/schema'; const schema = generateCardSchema(); ``` 构建还会在 `packages/schema/dist/ugc-card.schema.json` 处输出一个静态文件,发布为: ``` @safe-ugc-ui/schema/ugc-card.schema.json ``` ## 卡片模型 卡片是一个 JSON 对象,包含以下几个主要区域: - `meta`:卡片标识和版本元数据 - `assets`:必须使用 `@assets/...` 的命名资源引用;宿主提供最终的文件或 URL 映射 - `state`:通过 `{ "$ref": "$path.to.value" }` 引用的预计算值 - `styles`:用于 `$style` 重用的命名样式预设 - `fragments`:通过 `$use` 引用的可重用节点子树 - `views`:一个或多个可渲染的树 当前实现的节点类型: - `Box`、`Row`、`Column`、`Text`、`Image` - `Stack`、`Grid`、`Spacer`、`Divider`、`Icon` - `ProgressBar`、`Avatar`、`Badge`、`Chip`、`Button`、`Toggle`、`Accordion`、`Tabs` - `Switch`(结构化分支选择器) 支持的卡片级功能: - `$ref` 状态绑定 - 节点级 `$if` 条件渲染 - 带有静态 case 的结构化 `Switch` 分支选择 - `for...in` 循环 - `fragments` 加上 `$use` 子树重用 - 可重用的 `styles` 加上 `$style` 引用 - 针对宽度不超过 `768px` 的 container 的节点级 `responsive.medium` 覆盖 - 针对宽度不超过 `480px` 的 container 的节点级 `responsive.compact` 覆盖 - `hoverStyle` - 结构化 `transition` - 方向性 `borderRadius` - `objectFit`、`objectPosition`、`aspectRatio`、`backdropBlur` 和结构化 `clipPath` - `Button` / `Toggle` 禁用状态 - 带有隐藏内容预算的 `Accordion` 和 `Tabs` 本地交互状态 完整详情,请参阅: - [`safe-ugc-ui-card-spec.md`](./safe-ugc-ui-card-spec.md) - [`safe-ugc-ui-card-spec-lite.md`](./safe-ugc-ui-card-spec-lite.md) - [`safe-ugc-ui-card-spec.types.ts`](./safe-ugc-ui-card-spec.types.ts) ## 安全模型 JSON Schema 仅具有结构化功能。实际的安全检查位于 `@safe-ugc-ui/validator` 中。 推荐的宿主边界: - 在导入/摄取时为不受信任的原始 JSON 调用 `loadCardRaw()` - 仅当宿主已经解析了 payload 时才使用 `loadCard()` - 将 `validateRaw()` 和 `validate()` 视为较低级别的诊断 API - 将诸如 `renderTree()` 之类的低级别 renderer 内部实现视为假定已进行过验证的高级 API - 将卡片创作的 `state` 以及任何宿主提供的 runtime `state` 覆盖视为不受信任的输入 - 将最终的 `assets` 映射值视为宿主控制的输入;卡片作者可以引用 `@assets/...`,而 renderer 接受 HTTP(S)、blob、相对或安全的栅格 data-image URL,并由宿主决定实际来源和任何来源限制 - 让 `UGCRenderer` 在渲染前重新验证有效合并的 runtime 状态 - 如果宿主将 `hostOverflow` 设置为 `hidden` 以外的任何值,宿主必须提供外部包装器(例如 `overflow-x: auto`);否则溢出可能会泄漏到页面视口中 验证流水线强制执行: - 阻断用户控制的资源字段的外部 URL - 样式对象作为封闭的 DSL,因此未知的样式键会被拒绝而不是被忽略 - 针对 `@assets/...` 的路径遍历检查 - CSS 函数限制,例如 `url()`、`var()`、`calc()`、`expression()` - 布局隔离规则,例如禁止 `position: fixed` 和 `position: sticky` - 针对卡片大小、节点数量、循环次数、可渲染文本输出以及使用合并状态的有效样式输出的面向 runtime 的限制 - `$ref` 路径中的原型污染保护 `UGCContainer` 使用 `overflow: hidden`、`isolation: isolate`、 `contain: content` 和 `position: relative` 添加了 renderer 端的隔离,并且 `containerStyle` 无法覆盖这些键。宿主只能覆盖 overflow 键,并且只能通过 hostOverflow prop 进行覆盖。 ## 开发 ### 前置条件 - Node.js `>= 24` - pnpm `>= 11.17.0` ### 常用命令 ``` pnpm install pnpm build pnpm test pnpm test:run pnpm test:coverage pnpm release:pack-check pnpm release:check pnpm clean pnpm --filter @safe-ugc-ui/schema build pnpm --filter @safe-ugc-ui/demo dev ``` ## 仓库结构 ``` packages/ types/ Zod schemas, inferred TS types, constants schema/ JSON Schema generation and static schema artifact validator/ Validation pipeline and diagnostic result types react/ React renderer, components, asset/style/state helpers demo/ Vite playground ``` 测试与源码放在一起,以 `*.test.ts` 或 `*.test.tsx` 形式存在。 ## 维护者说明 - 当包版本、公共 API、 命令或工作流预期发生变化时,请同时更新 `README.md`、`AGENTS.md` 和 `CLAUDE.md`。 - 依赖维护是安全第一的:Dependabot 的常规版本更新 PR 已被禁用, 分组的安全更新 PR 保持启用状态,常规升级会在维护期间有意识地进行处理。 - GitHub CodeQL 默认设置通过仓库设置而不是提交的工作流文件执行 JavaScript/TypeScript 代码扫描。 - 发布版本由 GitHub Actions 在本地 clean-checkout `pnpm release:check` 预演通过后,通过 npm trusted publishing 从 `v*` 标签进行发布。 - 本地开发、CI 和 `publish.yml` 共享 Node `24` 和 pnpm `11.17.0` 工具基线;Node `24` 也满足 npm trusted publishing 的要求。 - 实际的发布步骤在 `publish.yml` 中通过 `pnpm -r publish --access public --no-git-checks` 运行,而不是作为普通的本地维护者命令运行。 - `pnpm release:pack-check` 会在发布前验证打包的 tarball,以便在 npm 看到它们之前检查导出的入口点和生成的产物。 - 将 `safe-ugc-ui-card-spec.md` 视为当前卡片行为的真实来源。 - 将 `safe-ugc-ui-spec-v0.3.md 视为设计历史,而不是当前的实现契约。 ## 许可证 [MIT](./LICENSE)
标签:JSON Schema, React, Syscalls, UGC内容安全, UI渲染引擎, Zod, 自动化攻击