☰
Native SDK Bridge 与安全能力全指南:JavaScript→Zig 调用、权限策略、窗口/WebView/对话框内置命令
2026/9/28 3:02:48 网站建设 项目流程
  • 桌面应用
  • 跨平台

【免费下载链接】native

Toolkit for building native desktop apps

项目地址:https://gitcode.com/gh_mirrors/ze/native
点击查看免费下载

导读

本文是 Native SDK(一个用于构建原生桌面应用的跨平台工具包)中「桥接(Bridge)、安全与原生能力」的完整技术指南,讲解 JavaScript 前端如何通过window.zero.invoke()调用原生 Zig 代码,以及桥接命令、内置命令、权限、窗口、子 WebView、对话框、导航策略与外部链接如何在一个"默认拒绝(default-deny)"的安全模型下协同工作。读完本文,你将能:注册并暴露自己的原生桥接命令、按命令与来源(origin)精确配置安全策略、从 JavaScript 启用窗口/分层 WebView/原生对话框等内置能力,并掌握全部错误码与限制,写出安全且可维护的混合应用。

本文以 skill-data/core/references/bridge-security-native-capabilities.md 为核心骨架,并结合仓库源码(src/bridge/root.zig、src/security/root.zig、src/runtime/builtin_bridge.zig 等)与类型声明(packages/native-sdk/native-sdk.d.ts)进行源码级佐证。

Bridge 架构:JavaScript 如何调用原生 Zig

在 Native SDK 中,JavaScript 通过一个统一的异步调用入口访问原生能力:

const result = await window.zero.invoke("native.ping", { source: "webview" });

window.zero.invoke(command, payload)是前端与原生世界的唯一信道:command是已注册的桥接命令名,payload是任意 JSON 值。该调用会打包为一个请求包进入运行时(Runtime),由运行时统一完成解析、检查、路由与响应。

运行时处理一次调用遵循固定的 5 步流程:

  1. 解析 JSON 请求:拆出id、command与原始payload三个字段。
  2. 强制消息大小限制:超限请求直接以payload_too_large拒绝。
  3. 检查来源(origin)与权限:命令策略要求来源匹配、权限齐备,否则拒绝。
  4. 查找已注册的处理器(handler):在注册表中按命令名查找;找不到返回unknown_command。
  5. 执行处理器并返回 JSON 响应:处理器结果经 JSON 合法性校验后封包返回。

这五步在源码中一一对应 src/bridge/root.zig 的Dispatcher.dispatch():先做max_message_bytes检查,再parseRequest(),随后policy.allows()做策略校验,接着registry.find()查处理器,最后执行handler.invoke_fn并用writeSuccessResponse包装结果(src/bridge/root.zig#L142-L164)。

关键安全前提是桥接命令默认拒绝(default-deny):一个命令必须同时满足"已在 Zig 中注册"与"被策略允许"两个条件,二者缺一不可。即便代码里注册了处理器,策略未放行时调用方仍会收到permission_denied。

Handler 模式:在 Zig 中注册桥接命令

应用侧在 Zig 中编写处理器函数,签名固定为接收context、invocation与输出缓冲区:

fn ping(context: *anyopaque, invocation: native_sdk.bridge.Invocation, output: []u8) anyerror![]const u8 { _ = invocation; const self: *App = @ptrCast(@alignCast(context)); self.ping_count += 1; return std.fmt.bufPrint(output, "{{\"message\":\"pong\",\"count\":{d}}}", .{self.ping_count}); }

要点:

  • context指向应用状态(本例中借它自增ping_count),通过@ptrCast(@alignCast(context))还原为*App;
  • output是运行时提供的结果缓冲区,处理器必须把合法 JSON写进去并返回切片;
  • invocation携带请求详情(request与source,其中source.origin是调用来源),不需要时用_ = invocation忽略。

接着用 Dispatcher 模式把处理器与策略组装起来:

fn bridge(self: *App) native_sdk.BridgeDispatcher { self.handlers = .{.{ .name = "native.ping", .context = self, .invoke_fn = ping }}; return .{ .policy = .{ .enabled = true, .commands = &policies }, .registry = .{ .handlers = &self.handlers }, }; }

其中policies是需要单独定义的命令策略数组(见下文"安全策略")。Dispatcher在源码中是policy + registry + async_registry的组合(src/bridge/root.zig),还支持异步处理器AsyncHandler(通过AsyncResponder.success/fail延迟应答,适合耗时操作)。

返回用户可控字符串时务必转义,否则会产生"注入无效 JSON"乃至响应走私风险。正确写法是:

return native_sdk.bridge.writeJsonStringValue(output, user_name);

writeJsonStringValue会把字符串写成合法 JSON 字符串字面量(含引号、反斜杠转义)。仓库测试src/bridge/root.zig#L408-L421验证了这一点:未经转义的hello "user"会被判定为非法 JSON 并以handler_failed拒绝(writeSuccessResponse内部先做json.isValidValue校验,非法即降级为错误响应)。

消息大小限制

桥接信道对每条消息都有硬性上限,超出即拒绝:

项限制
请求消息(request message)16 KiB
响应(response)16 KiB
处理器结果(handler result)12 KiB
请求 ID64 字节
命令名(command name)128 字节

需要说明的是,skill 文档给出的 16 KiB 是编写桥接代码时应遵循的安全默认值;运行时底层还定义了更宽的物理上限——src/bridge/root.zig 中max_message_bytes、max_response_bytes、max_result_bytes均为 1 MiB,max_id_bytes = 64、max_command_bytes = 128,命令名与 ID 上限与文档一致。parseRequest与validId/validCommand会分别校验 ID 与命令名的字符集(禁止控制字符、引号、反斜杠、空格与/,见 src/bridge/root.zig)。

大数据传输建议:不要把大数据硬塞进单条桥接响应。正确做法是:

  • 将大数据写入原生文件/资源,通过桥接只传递引用(路径、ID);
  • 或采用分块(chunking)模式,将数据切分为多条小消息分批传输;
  • 避免把整个文件内容、大型图片或日志塞进一个invoke返回值。

安全策略:默认拒绝,精确放行

核心默认值

Native SDK 的安全模型以"最小权限"为出发点,以下是默认行为,未配置即拒绝:

  • 不授予任何权限,除非在权限列表中显式列出;
  • 拒绝所有桥接命令,除非策略允许该命令;
  • 阻止导航,除非目标 origin 在导航允许列表中;
  • 拒绝外部链接,除非显式配置;
  • 始终拒绝对话框内置命令,除非在builtin_bridge中显式列出。

Manifest 配置示例

桥接命令与安全策略既可写在应用清单(app.json/app.zon),也可在运行时的策略对象中直接配置。以下为清单(ZON 语法)示例:

.permissions = .{ "window" }, .capabilities = .{ "webview", "js_bridge" }, .bridge = .{ .commands = .{ .{ .name = "native.ping", .origins = .{ "zero://app" } }, }, }, .security = .{ .navigation = .{ .allowed_origins = .{ "zero://app", "http://127.0.0.1:5173" }, .external_links = .{ .action = "deny" }, }, },

逐项解读:

  • permissions:应用级权限,例如"window"、"filesystem"、"clipboard"、"notifications"等(完整常量见 src/security/root.zig:permission_window、permission_command、permission_view、permission_dialog、permission_filesystem、permission_clipboard、permission_network、permission_notifications、permission_credentials);
  • capabilities:声明应用启用的能力域,例如"webview"(WebView 层)、"js_bridge"(JS 桥接)、"native_views"、"gpu_surfaces"等(完整枚举见 packages/native-sdk/schemas/app.schema.json);
  • bridge.commands:命令级策略,每条含name、可选的permissions与origins;
  • security.navigation.allowed_origins:主框架导航允许的 origin,本地应用内容常用zero://app,开发服务器常用http://127.0.0.1:5173;
  • security.navigation.external_links:外部链接策略,action可选"deny"或"open_system_browser"(src/security/root.zig 定义了ExternalLinkAction与ExternalLinkPolicy;allowsExternalUrl支持精确 URL 与带*前缀通配,且通配必须包含合法 scheme 与路径前缀,防止example.com.evil绕过,见 src/security/root.zig)。

Origin 与权限的判定逻辑

策略判定在源码中由Policy.allows()完成(src/bridge/root.zig):

  1. enabled为 false 直接拒绝;
  2. 未在commands中找到命令名则拒绝;
  3. 策略要求的每个权限都必须在运行时权限集中(security.hasPermissions,要求全部满足,见 src/security/root.zig)——注意:若策略本身配置了permissions列表,会与运行时权限取交集校验;
  4. origin 校验:origins为空表示任意来源;否则逐条精确匹配,"*"放行一切。

实践原则:优先使用精确 origin,而非"*"。"*"只应留给"不暴露任何原生状态"的命令,且仅当项目已经明确接受该风险时使用。这与 skill-data/core/SKILL.md 中"偏好精确的安全策略变更而非宽泛放行"的总体准则一致。

内置命令:窗口、分层 WebView 与对话框

除应用自定义命令外,Native SDK 自带一批内置桥接命令,覆盖窗口、分层 WebView 与对话框。它们与应用自定义命令分开控制:必须通过builtin_bridge显式启用。

窗口命令(Window)

  • native-sdk.window.list
  • native-sdk.window.create
  • native-sdk.window.focus
  • native-sdk.window.close

分层 WebView 命令(Layered WebView)

  • native-sdk.webview.create
  • native-sdk.webview.list
  • native-sdk.webview.setFrame
  • native-sdk.webview.navigate
  • native-sdk.webview.setZoom
  • native-sdk.webview.setLayer
  • native-sdk.webview.close

对话框命令(Dialog)

  • native-sdk.dialog.openFile
  • native-sdk.dialog.saveFile
  • native-sdk.dialog.showMessage

这些命令的底层分发实现在 src/runtime/builtin_bridge.zig:dispatchWindowBridgeCommand(窗口四命令)、dispatchWebViewBridgeCommand(WebView 七命令,且原生-only 构建会以WebViewLayerNotBuilt教学式错误回应而非误导性的 not-found,见 src/runtime/builtin_bridge.zig)、dispatchDialogBridgeCommand(对话框三命令)分别路由到对应的 JSON 解析与平台服务调用。

显式启用内置命令

const app_permissions = [_][]const u8{native_sdk.security.permission_window}; .security = .{ .permissions = &app_permissions, .navigation = .{ .allowed_origins = &.{ "zero://app" } }, }, .builtin_bridge = .{ .enabled = true, .commands = &.{ .{ .name = "native-sdk.window.create", .permissions = .{ "window" }, .origins = .{ "zero://app" } }, .{ .name = "native-sdk.webview.create", .permissions = .{ "window" }, .origins = .{ "zero://app" } }, .{ .name = "native-sdk.dialog.openFile", .origins = .{ "zero://app" } }, }, },

配置要点:

  • builtin_bridge.enabled = true是总开关;每条commands条目仍要单独列出命令名、所需权限与允许的 origin;
  • 窗口/子 WebView 类命令通常要求"window"权限(当应用配置了权限列表时,见 src/runtime/builtin_bridge.zig 的allowsBuiltinBridgeCommand);
  • 对话框命令始终默认拒绝,必须显式列出;
  • js_window_api = true会额外暴露window.zero.windows.*与window.zero.webviews.*这两个便捷 API,但它不会绕过 origin 或权限检查——所有调用仍走同一套策略判定。

从 JavaScript 管理窗口

启用js_window_api与相应builtin_bridge策略后,前端可以直接创建、枚举、聚焦与关闭原生窗口:

const win = await window.zero.windows.create({ label: "tools", title: "Tools", width: 420, height: 320, }); const all = await window.zero.windows.list(); await window.zero.windows.focus(win.id); await window.zero.windows.close(win.id);

create返回的win.id是窗口句柄;windows.list()返回所有窗口信息。窗口的创建参数在 packages/native-sdk/native-sdk.d.ts 中有完整类型定义:除label/title/width/height/x/y外,还支持restoreState(默认true)、titlebar(standard/hidden_inset/hidden_inset_tall/chromeless)、transparent、alwaysOnTop、clickThrough、activateOnShow与可选的url初始源。窗口选择的底层实现支持按id或按label解析(resolveWindowSelector,见 src/runtime/builtin_bridge.zig)。

窗口状态持久化依赖稳定的 label:请为窗口使用有意义的标签,如main、settings、tools、preview,这样窗口的几何状态、显示/隐藏策略才能在重启后稳定恢复(restore_state)。

分层 WebViews(Layered WebViews)

子 WebView 是叠放在原生窗口内部的原生 WebView,适合做预览面板、浏览器风格标签页、辅助工具栏等。前端创建与操控示例:

const preview = await window.zero.webviews.create({ label: "preview", url: "https://example.com", frame: { x: 24, y: 24, width: 480, height: 320 }, layer: 10, bridge: false, }); await preview.setZoom(1.25); await preview.setLayer(20); await preview.close();

create的完整选项见 packages/native-sdk/native-sdk.d.ts:label(默认"webview")、windowId(父窗口 id,默认即调用方窗口)、url(其 origin 必须通过运行时导航策略)、frame(相对父窗口的逻辑坐标)、layer(原生 z 序,越大越靠上)、transparent(尽力透明背景)与bridge(是否注入window.zero)。返回的句柄带有setFrame/navigate/setZoom/setLayer/close方法(packages/native-sdk/native-sdk.d.ts);setZoom的合法范围为0.25到5.0(源码中越界返回InvalidWebViewOptions,见 src/runtime/builtin_bridge.zig)。

分层 WebView 规则:

  • WebView 的 URL 必须通过导航策略(其 origin 须在security.navigation.allowed_origins内),源码在createWebViewFromJson中通过validateWebViewUrl校验(src/runtime/builtin_bridge.zig);
  • 命令只作用于调用方所在的原生窗口:windowId默认解析为调用窗口,传入时必须一致;
  • main标签保留给启动 WebView,子 WebView 不得使用(validateChildWebViewLabel会拒绝main,见 src/runtime/validation.zig),main也不能被navigate/close操作;
  • 子 WebView只有以bridge: true创建时才接收window.zero——默认false,这防止了不受信任内容获得原生能力;
  • 后端缺口(如平台不支持某操作)应以invalid_request拒绝,而不是静默成功。

对话框:原生文件选择与消息框

对话框属于高风险原生能力,必须显式配置builtin_bridge策略后才能调用。三个内置命令对应三种场景:

const files = await window.zero.invoke("native-sdk.dialog.openFile", { title: "Select a file", defaultPath: "/home", allowMultiple: true, allowDirectories: false, }); const path = await window.zero.invoke("native-sdk.dialog.saveFile", { title: "Save as", defaultName: "untitled.txt", }); const result = await window.zero.invoke("native-sdk.dialog.showMessage", { style: "warning", title: "Confirm", message: "Delete this item?", primaryButton: "Delete", secondaryButton: "Cancel", });

参数说明:

  • openFile:title、defaultPath(起始目录)、allowMultiple(多选)、allowDirectories(是否允许选目录)与可选的filters(文件类型过滤);
  • saveFile:title、defaultName(默认文件名)与defaultPath;
  • showMessage:style(如"warning")、title、message、primaryButton/secondaryButton按钮文本。

这些选项在进入平台层前都会经过严格校验(字段长度上限、禁止空字符等,见 src/runtime/validation.zig 的validateOpenDialogOptions/validateSaveDialogOptions/validateMessageDialogOptions),对话框命令本身则由dispatchDialogBridgeCommand路由到系统服务(src/runtime/builtin_bridge.zig)。

安全准则:只对可信的应用 UI 使用原生对话框;绝不要向远程或不受信任的 origin 暴露任意的文件系统访问能力。

错误处理:统一错误码

所有桥接调用失败时,Promise 都会 reject,错误对象带error.code与error.message:

错误码触发场景
invalid_request输入畸形、不支持的操作、导航 URL 被拒、目标缺失、重复/保留标签
unknown_command没有注册对应处理器
permission_deniedorigin 或权限检查失败
handler_failed处理器返回错误(或返回了非法 JSON)
payload_too_large请求超过大小限制
internal_error运行时意外故障

错误码枚举定义于 src/bridge/root.zig,对应的 TypeScript 类型为NativeSdkErrorCode(packages/native-sdk/native-sdk.d.ts)。注意判定的优先级:策略拒绝先于命令未注册——源码测试"dispatcher reports permission denial before unknown command"明确验证了未启用策略的native.ping返回permission_denied而非unknown_command(src/bridge/root.zig)。

前端代码必须始终处理错误:

try { await window.zero.invoke("native.save", payload); } catch (error) { console.error(error.code, error.message); }

不要只处理成功路径;尤其是用户可控输入或来自远程页面的调用,务必捕获并区分permission_denied(策略问题)与invalid_request(参数问题)以便定位。

源码佐证与验证手段

  • 桥接核心:src/bridge/root.zig 包含Dispatcher、Policy、Registry、parseRequest、响应序列化与全套单元测试(请求解析、畸形/超限拒绝、策略与 origin 校验、非法结果 JSON 拒绝等),是理解整条链路的权威入口;
  • 安全策略:src/security/root.zig 定义权限常量、NavigationPolicy、ExternalLinkPolicy与hasPermission/allowsOrigin/allowsExternalUrl判定函数及测试;
  • 内置命令分发:src/runtime/builtin_bridge.zig 是 window/webview/view/dialog/os/credentials/clipboard/command 各内置命令的 JSON 解析与平台服务桥接层;
  • 输入校验:src/runtime/validation.zig 覆盖命令名、标签、对话框选项、通知、凭据等字段的长度与字符校验;
  • JS 类型契约:packages/native-sdk/native-sdk.d.ts 提供window.zero相关 API 的完整 TypeScript 定义(NativeSdkWindowInfo、NativeSdkWebViewHandle、NativeSdkInvokeError等);
  • 清单 Schema:packages/native-sdk/schemas/app.schema.json 定义了app.json中bridge、security、permissions、capabilities、windows等字段的合法取值与默认值。

综合来看,Native SDK 的桥接体系以"解析 → 限流 → 鉴权 → 路由 → 执行 → 校验响应"的流水线为基础,以"默认拒绝、精确放行"为安全内核;应用自定义命令与内置命令(窗口、WebView、对话框)共用同一套 origin/permission 校验,只是启用入口不同。遵循本文的配置示例与大小、标签、origin 约束,即可在获得原生窗口、分层 WebView 与原生对话框能力的同时,守住前端与原生边界的安全底线。

  • 桌面应用
  • 跨平台

【免费下载链接】native

Toolkit for building native desktop apps

项目地址:https://gitcode.com/gh_mirrors/ze/native
点击查看免费下载

相关推荐

上一篇:preact-render-to-string性能优化:5个技巧让你的渲染速度提升300%
下一篇:Coordinators实战教程:构建一个简单的井字棋游戏

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询