安全机制 (Security)
zero-native 默认将 WebView 视为不可信环境。应用开发者需要通过显式的权限、命令策略和导航规则来逐步开启原生能力。
权限与能力 (Permissions and Capabilities)
capabilities(能力)描述了应用所使用的大致特性。permissions(权限)则是运行时在执行原生指令前校验的授权凭证。
.permissions = .{ "window", "filesystem" },
.capabilities = .{ "webview", "js_bridge" },可用权限列表
| 权限 | 授予的操作 |
|---|---|
window | 窗口创建/聚焦/关闭操作,以及分层 WebView 管理 |
filesystem | 通过桥接命令访问文件系统 |
clipboard | 剪贴板读取/写入 |
network | 原生代码发起网络请求 |
camera | 相机访问 |
microphone | 麦克风访问 |
location | 定位服务 |
notifications | 系统通知 |
自定义权限可以使用反向域名(reverse-DNS)命名(例如 com.example.my-permission)。请使用满足应用需求的最小权限集。
可用能力列表
| 能力 | 描述 |
|---|---|
webview | WebView 页面渲染 |
js_bridge | JavaScript 桥接 |
native_module | 原生 Zig 扩展模块 |
filesystem | 文件系统访问 |
network | 网络访问 |
clipboard | 剪贴板访问 |
原生命令 (Native Commands)
原生桥接命令采用“默认拒绝”(Default-Deny)策略。在运行时调用命令之前,该命令必须已经由原生代码注册,并且处于策略的允许列表中。
.bridge = .{
.commands = .{
.{
.name = "native.ping",
.origins = .{ "zero://app" },
},
.{
.name = "zero-native.window.create",
.permissions = .{ "window" },
.origins = .{ "zero://app" },
},
},
},建议使用确切的源(Origins)而不是通配符 "*"。仅在本地开发调试,或者命令不会暴露任何原生状态/敏感信息时才使用 "*"。
内置桥接策略 (Builtin Bridge Policy)
zero-native 提供了用于窗口(zero-native.window.*)、分层 WebView(zero-native.webview.*)和对话框(zero-native.dialog.*)的内置命令。这些命令通过 RuntimeOptions 中的 builtin_bridge 字段进行控制,与应用自定义的命令分开管理。
js_window_api 在 JavaScript 中暴露了 window 和 WebView 辅助函数,但这不会绕过安全限制。窗口命令(zero-native.window.list, create, focus, close)和 WebView 命令(zero-native.webview.create, list, setFrame, navigate, setZoom, setLayer, close)必须由允许的源发起,并且在配置了运行时权限时必须具备 window 权限。WebView 命令只能针对发送该桥接消息的窗口起效。同时,WebView 加载的 URL 必须在 security.navigation.allowed_origins 的允许列表中,且子 WebView 仅在以 bridge: true 创建时才会注入 window.zero。
为了实施更细粒度的控制,建议使用显式的 builtin_bridge 策略。选择此方式时,必须列出应用需要调用的每一条内置命令。对话框命令(zero-native.dialog.openFile, saveFile, showMessage)默认是完全拒绝的,必须在 builtin_bridge 策略列表中显式配置:
.builtin_bridge = .{
.enabled = true,
.commands = &.{
.{ .name = "zero-native.window.create", .permissions = .{ "window" }, .origins = .{ "zero://app" } },
.{ .name = "zero-native.webview.create", .permissions = .{ "window" }, .origins = .{ "zero://app" } },
.{ .name = "zero-native.webview.list", .permissions = .{ "window" }, .origins = .{ "zero://app" } },
.{ .name = "zero-native.webview.setFrame", .permissions = .{ "window" }, .origins = .{ "zero://app" } },
.{ .name = "zero-native.webview.navigate", .permissions = .{ "window" }, .origins = .{ "zero://app" } },
.{ .name = "zero-native.webview.setZoom", .permissions = .{ "window" }, .origins = .{ "zero://app" } },
.{ .name = "zero-native.webview.setLayer", .permissions = .{ "window" }, .origins = .{ "zero://app" } },
.{ .name = "zero-native.webview.close", .permissions = .{ "window" }, .origins = .{ "zero://app" } },
.{ .name = "zero-native.dialog.openFile", .origins = .{ "zero://app" } },
.{ .name = "zero-native.dialog.showMessage", .origins = .{ "zero://app" } },
},
},桥接错误代码 (Bridge Error Codes)
当桥接调用失败时,JavaScript 的 Promise 会被拒绝,并返回一个包含 code 字段的错误:
| 错误码 | 原因 |
|---|---|
invalid_request | 格式错误的输入、不支持的内置操作、被拒绝的导航 URL、不存在的窗口/WebView、重复或保留的 WebView 标签,或其它可由调用方修正的请求问题 |
unknown_command | 该命令没有注册对应的处理函数 |
permission_denied | 源(Origin)或权限校验失败 |
handler_failed | 处理函数内部返回了错误 |
payload_too_large | 消息体积超过了 16 KiB 的限制 |
internal_error | 未预期的运行时内部错误 |
在 JavaScript 中处理错误:
try {
const result = await window.zero.invoke("native.ping", {});
} catch (error) {
console.error(error.code, error.message);
}导航策略 (Navigation Policy)
主框架(Main-Frame)的导航采用白名单机制。打包的静态资源通常使用 zero://app,内联示例使用 zero://inline,而本地开发服务器应该列出其确切的本地源(Origin)。
.security = .{
.navigation = .{
.allowed_origins = .{
"zero://app",
"zero://inline",
"http://127.0.0.1:5173",
},
},
},所有未知的页面主导航都会被拦截,除非其在外部链接策略中显式定义了处理方式。
外部链接 (External Links)
默认情况下拒绝所有外部链接跳转。如果您希望在系统浏览器中打开某些链接,需要主动启用并列出 URL 前缀匹配规则:
.security = .{
.navigation = .{
.external_links = .{
.action = "open_system_browser",
.allowed_urls = .{ "https://example.com/docs/*" },
},
},
},请不要为可能受到远程内容影响的页面配置过于宽泛的外部链接匹配模式。
CSP 指南 (CSP Guidance)
对于打包的离线资产,建议使用严格的网页内容安全策略(Content Security Policy):
<meta http-equiv="Content-Security-Policy"
content="default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data:; connect-src 'self'">对于需要嵌入脚本或样式的内联 Zig 示例,仅添加最小限度的内联(inline)权限:
<meta http-equiv="Content-Security-Policy"
content="default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src 'self'">对于开发服务器,仅将 connect-src 扩展至框架所需的本地开发源和 WebSocket 端口。注意保持生产环境的 CSP 配置与开发环境完全独立。
安全模型概览 (Security Model Summary)
| 层次 | 默认状态 | 开启条件 (Opt-in) |
|---|---|---|
| 应用桥接命令 | 拒绝 | 带源和权限验证的单命令策略 |
| 内置桥接 (窗口与分层 WebView) | 除非 js_window_api 或显式策略允许该辅助函数且通过了源/权限检查,否则拒绝 | window 权限以及确切允许的源列表 |
| 内置桥接 (对话框) | 拒绝 | 显式配置 builtin_bridge 策略列表 |
| 导航 | 拦截 | 允许源白名单 |
| 外部链接 | 拒绝 | 显式动作 (action) + URL 前缀列表 |
| 运行时权限 | 无任何权限 | 在 app.zon 中声明,在运行时动态校验 |
| CSP | zero-native 默认不强制实施 | 在您的 HTML <meta> 标签中配置 |
设计的核心目标是深度防御(Defense in Depth):即便在 Zig 原生代码中注册了某条指令,若请求源不匹配,或者缺少所需的运行时权限授权,该命令在运行时也绝不会执行。