Rust in Everything
基于 tauri 2.12.5

写给一个窗口的 deny 对所有窗口生效

主线位置:调用主线上 on_message 调 RuntimeAuthority::resolve_access 的那一步。允许和拒绝在编译期分开存、运行时由 resolve_access 判断,见编译期解析和运行时检查;后者的「动脑筋」用 MockRuntime 演示过这条规则,本页在真实窗口里复现它,并给出两种改法。

flowchart TB
  req["kiosk 以外的窗口调 greet"] --> den{"有来源匹配的 deny"}
  den -->|"kiosk 能力的 deny 是 local"| rej["explicitly denied"]
  den -->|否| allow{"允许条目 来源和标签都匹配"}
  allow -->|是| ok["放行"]
  allow -->|否| no["not allowed on window"]

实验 crate exp-tauri/pit3-deny:由 pit3-acl 复制(有应用清单,命令 greet、report),setup 按环境变量建 main、settings、kiosk 三个隐藏窗口,每个页面调一次 greet 并报告。tauri =2.12.5,--features tauri/custom-protocol,平台 M2 Max、macOS 27、rustc 1.99.0。

现象

本来所有窗口都能调 greet。后来加了一个展示用的 kiosk 窗口,不该能调它,于是加一份只针对 kiosk 的能力放一条拒绝:

{
  "identifier": "main",
  "windows": ["*"],
  "permissions": ["core:default", "allow-greet"]
}
{
  "identifier": "kiosk",
  "windows": ["kiosk"],
  "permissions": ["deny-greet"]
}

(deny-greet 是 tauri-build 为每个应用命令自动生成的那对权限之一,在 permissions/autogenerated/greet.toml 里。)debug 构建:

REPORT [settings] greet -> err greet explicitly denied on origin local

referenced by: capability: kiosk, permission: deny-greet
REPORT [main] greet -> err greet explicitly denied on origin local

referenced by: capability: kiosk, permission: deny-greet
REPORT [kiosk] greet -> err greet explicitly denied on origin local

referenced by: capability: kiosk, permission: deny-greet

三个窗口全被拒,原因都指向 kiosk 能力。release 构建只剩 Command greet not allowed by ACL,三行一样,看不出和 kiosk 有关:

REPORT [settings] greet -> err Command greet not allowed by ACL
REPORT [kiosk] greet -> err Command greet not allowed by ACL
REPORT [main] greet -> err Command greet not allowed by ACL

应用里有没有叫 kiosk 的窗口都一样:运行时检查的动脑筋里,拒绝写在一个根本不存在的窗口上,照样拦住了 main。

出处

resolve_access 的文档注释把规则写得很直白:

  /// Checks if the given IPC execution is allowed and returns the [`ResolvedCommand`] if it is.
  ///
  /// A command is denied when a capability that denies it matches the given origin
  /// (execution context), window and webview labels are not taken into account for denial.

它先问 is_denied,后者调 denied_on,只用 origin.matches(&cmd.context) 过滤拒绝条目;有一条命中就返回 None,允许条目根本不看。kiosk 能力的 local 默认为真,它的拒绝条目来源是 Local,任何本地页面的来源也是 Local,于是命中。能力里的 windows 对拒绝条目来说只被记下,不参与判断。

修法

改法一:收窄允许的一侧,不写拒绝。 「kiosk 不能调」的意思就是「不把它给 kiosk」:

{
  "identifier": "main",
  "windows": ["main", "settings"],
  "permissions": ["core:default", "allow-greet"]
}
{
  "identifier": "kiosk",
  "windows": ["kiosk"],
  "permissions": ["core:default"]
}
REPORT [main] greet -> ok "hello, rie"
REPORT [settings] greet -> ok "hello, rie"
REPORT [kiosk] greet -> err greet not allowed on window "kiosk", webview "kiosk", URL: local

allowed on: [windows: "main", "settings", URL: local]

referenced by: capability: main, permission: allow-greet

窗口多、只想排除一个时,windows 写枚举,或者给要排除的窗口起一个不被 glob 覆盖的标签,比如其他窗口都叫 app-*。

改法二:deny 只用来按来源划界。 拒绝看来源,所以它适合的场景是「本地页面能调、远程页面不能调」。把允许同时给本地和远程,再用一份 "local": false 的能力只对远程拒绝:

{
  "identifier": "main",
  "windows": ["*"],
  "remote": { "urls": ["http://127.0.0.1:19010"] },
  "permissions": ["allow-greet"]
}
{
  "identifier": "no-remote-greet",
  "windows": ["*"],
  "local": false,
  "remote": { "urls": ["http://127.0.0.1:19010"] },
  "permissions": ["deny-greet"]
}

main 窗口加载本地页面,web 窗口加载 http://127.0.0.1:19010/deny.html(本机 python3 -m http.server):

REPORT [main] greet -> ok "hello, rie"
REPORT [web] greet -> err greet explicitly denied on origin remote: http://127.0.0.1:19010/

referenced by: capability: no-remote-greet, permission: deny-greet

本地不受影响,远程被拒。这个例子里直接不给远程允许效果相同;拒绝的价值在于压过别处的允许,比如某个插件的 default 权限集里带了你不想给远程页面的命令。

怎么确认没中招

  • 搜能力文件里的 deny-。每一条都要能回答「它在哪个来源下生效」:没写 "local": false 的,作用于全部本地窗口,不管这份能力的 windows 写了什么。
  • debug 构建的拒绝信息以 explicitly denied on origin 开头时,referenced by 指出是哪份能力的哪条拒绝,从那里查。
  • 插件的权限集也可能带拒绝;按 referenced by 里的权限名去插件的权限文件里找。

小结

  • 拒绝只按来源(本地 / 远程 URL)匹配,能力的 windows、webviews 对拒绝不起作用;一条本地的 deny 拦住所有本地窗口。
  • release 下这种拒绝和其他拒绝同一句话,debug 下是 explicitly denied on origin local,并指出出处。
  • 想让某个窗口不能调某个命令,收窄允许一侧的 windows,不要写 deny。
  • deny 适合「远程不能调」这类按来源的划界,配 "local": false 使用。

💬 关联讨论

发起讨论
加载中...