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, 自动化攻击