写给一个窗口的 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使用。