feat(acl): gate every app command per window via a Tauri AppManifest
build.rs now derives an AppManifest from generate_handler!, which makes tauri 2.11 apply the ACL to app commands (it skips them entirely without one). default.json grants the 110 main-window commands, file-viewer.json the five viewer_* commands, and build.rs refuses to build on a missing, misspelled, duplicated, misfiled or deny-* grant, or on a hand-written permission file. Stale autogenerated permissions are pruned per build. Closes the residual risk recorded by the terminal file viewer: a compromised viewer window could invoke any app command. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -912,7 +912,10 @@ mod tests {
|
||||
})
|
||||
.collect();
|
||||
|
||||
let mut sorted = listed.clone();
|
||||
// Plugin and core grants: the exact reviewed list, unchanged by the lockdown.
|
||||
let (bare, prefixed): (Vec<String>, Vec<String>) =
|
||||
listed.iter().cloned().partition(|g| !g.contains(':'));
|
||||
let mut sorted = prefixed;
|
||||
sorted.sort();
|
||||
let mut expected = vec![
|
||||
"core:event:allow-listen",
|
||||
@@ -924,11 +927,31 @@ mod tests {
|
||||
expected.sort();
|
||||
assert_eq!(
|
||||
sorted, expected,
|
||||
"the capability set changed. That is allowed — but it is the IPC \
|
||||
"the plugin/core capability set changed. That is allowed — but it is the IPC \
|
||||
surface a compromised webview can call, so update this list \
|
||||
deliberately rather than to make the test pass."
|
||||
);
|
||||
|
||||
// App commands: since build.rs declares the AppManifest, the bare `allow-*` grants
|
||||
// are the complete list of app commands the main window may call. `build.rs` already
|
||||
// fails the build when they disagree with generate_handler!; this keeps the reviewed
|
||||
// rule ("every non-viewer command, exactly") visible where the plugin census lives.
|
||||
let registered = crate::command_census::registered_commands(include_str!("lib.rs"))
|
||||
.expect("lib.rs should contain a generate_handler! list");
|
||||
let mut expected_bare: Vec<String> = registered
|
||||
.iter()
|
||||
.filter(|c| crate::command_census::expected_windows(c) == ["main"])
|
||||
.map(|c| crate::command_census::allow_permission(c))
|
||||
.collect();
|
||||
expected_bare.sort();
|
||||
let mut bare = bare;
|
||||
bare.sort();
|
||||
assert_eq!(
|
||||
bare, expected_bare,
|
||||
"default.json's app-command grants must be exactly the main-window commands"
|
||||
);
|
||||
assert!(bare.len() >= 100, "the census found {} app grants; the parser has stopped seeing the list", bare.len());
|
||||
|
||||
// Belt and braces: the `*:default` aliases are the specific trap here,
|
||||
// because they expand to a set the file never spells out. `store:*` in
|
||||
// particular was an arbitrary host-file read/write primitive.
|
||||
|
||||
Reference in New Issue
Block a user