fix(acl): refuse capabilities the census cannot see, and name the failed check
tauri-build loads capabilities/**/*.{json,toml,json5} plus inline
app.security.capabilities from any tauri config or TAURI_CONFIG, but the
census read only top-level capabilities/*.json, so a .toml, a
subdirectory or an inline capability could over-grant a window with a
green build. build.rs now fails on any of those, on a JSON5/TOML tauri
config it cannot read, and on a `webviews` or `remote` key in a
capability file. OS/editor junk (.DS_Store, swap files) that tauri never
loads is skipped in capabilities/ and permissions/. Each failure is
headed by the check that failed rather than always "capabilities do not
match generate_handler!".
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
+121
-35
@@ -4,6 +4,11 @@
|
|||||||
//! 2.11 skips the ACL for app commands entirely (`webview/mod.rs:1794`), so any local window
|
//! 2.11 skips the ACL for app commands entirely (`webview/mod.rs:1794`), so any local window
|
||||||
//! could call any command.
|
//! could call any command.
|
||||||
//!
|
//!
|
||||||
|
//! Because the census can only vouch for what it reads, the build also stops on any capability
|
||||||
|
//! tauri would load that the census does not: anything in `capabilities/` other than a
|
||||||
|
//! top-level `*.json`, a `webviews`/`remote` key, `app.security.capabilities` in a tauri config
|
||||||
|
//! or `TAURI_CONFIG`, and any hand-written file under `permissions/`.
|
||||||
|
//!
|
||||||
//! The parser and the rules live in `src/command_census.rs`, which `cargo test` also compiles,
|
//! The parser and the rules live in `src/command_census.rs`, which `cargo test` also compiles,
|
||||||
//! so they have unit tests. Spec: `docs/superpowers/specs/2026-09-22-app-manifest-lockdown-design.md`.
|
//! so they have unit tests. Spec: `docs/superpowers/specs/2026-09-22-app-manifest-lockdown-design.md`.
|
||||||
|
|
||||||
@@ -12,10 +17,12 @@ mod command_census;
|
|||||||
|
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
fn fail(problems: &[String]) -> ! {
|
/// Stops the build. `what` names the check that failed, so a malformed capability file, a
|
||||||
|
/// stray entry or a hand-written permission does not read as a grant/handler mismatch.
|
||||||
|
fn fail(what: &str, problems: &[String], hint: &str) -> ! {
|
||||||
eprintln!();
|
eprintln!();
|
||||||
eprintln!(
|
eprintln!(
|
||||||
"capabilities do not match generate_handler! ({} problem{}):",
|
"{what} ({} problem{}):",
|
||||||
problems.len(),
|
problems.len(),
|
||||||
if problems.len() == 1 { "" } else { "s" }
|
if problems.len() == 1 { "" } else { "s" }
|
||||||
);
|
);
|
||||||
@@ -23,49 +30,52 @@ fn fail(problems: &[String]) -> ! {
|
|||||||
eprintln!(" - {p}");
|
eprintln!(" - {p}");
|
||||||
}
|
}
|
||||||
eprintln!();
|
eprintln!();
|
||||||
eprintln!(
|
eprintln!("{hint}");
|
||||||
"Every app command needs exactly one bare `allow-<command-with-dashes>` grant: \
|
|
||||||
`viewer_*` commands in capabilities/file-viewer.json, everything else in \
|
|
||||||
capabilities/default.json. See src/command_census.rs."
|
|
||||||
);
|
|
||||||
eprintln!();
|
eprintln!();
|
||||||
std::process::exit(1);
|
std::process::exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const LAYOUT_HINT: &str = "Every capability is a top-level capabilities/*.json file with a \
|
||||||
|
`windows` list and no `webviews` or `remote`, and no capability is declared anywhere else \
|
||||||
|
(tauri.conf.json, TAURI_CONFIG, subdirectories, .toml/.json5). The census in \
|
||||||
|
src/command_census.rs can only vouch for what it reads.";
|
||||||
|
|
||||||
|
fn file_name(path: &Path) -> String {
|
||||||
|
path.file_name()
|
||||||
|
.expect("a directory entry has a file name")
|
||||||
|
.to_string_lossy()
|
||||||
|
.into_owned()
|
||||||
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
// tauri-build already emits rerun-if-changed for `capabilities` and `permissions`.
|
// tauri-build already emits rerun-if-changed for `capabilities`, `permissions` and the
|
||||||
|
// tauri config files, and rerun-if-env-changed for TAURI_CONFIG.
|
||||||
println!("cargo:rerun-if-changed=src/lib.rs");
|
println!("cargo:rerun-if-changed=src/lib.rs");
|
||||||
println!("cargo:rerun-if-changed=src/command_census.rs");
|
println!("cargo:rerun-if-changed=src/command_census.rs");
|
||||||
|
|
||||||
let lib_rs = std::fs::read_to_string("src/lib.rs")
|
let lib_rs = std::fs::read_to_string("src/lib.rs")
|
||||||
.expect("build.rs runs with CWD = src-tauri, so src/lib.rs must be readable");
|
.expect("build.rs runs with CWD = src-tauri, so src/lib.rs must be readable");
|
||||||
let Some(commands) = command_census::registered_commands(&lib_rs) else {
|
let Some(commands) = command_census::registered_commands(&lib_rs) else {
|
||||||
fail(&["src/lib.rs has no `generate_handler![ … ])` block to derive the AppManifest from"
|
fail(
|
||||||
.to_string()]);
|
"missing generate_handler! block",
|
||||||
|
&["src/lib.rs has no `generate_handler![ … ])` block to derive the AppManifest from"
|
||||||
|
.to_string()],
|
||||||
|
"build.rs derives the AppManifest from that block; see src/command_census.rs.",
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut files = Vec::new();
|
check_tauri_config();
|
||||||
for entry in std::fs::read_dir("capabilities").expect("capabilities/ must exist") {
|
let files = read_capabilities();
|
||||||
let path = entry.expect("readable entry in capabilities/").path();
|
|
||||||
if path.extension().is_some_and(|e| e == "json") {
|
|
||||||
let name = path
|
|
||||||
.file_name()
|
|
||||||
.expect("a file name")
|
|
||||||
.to_string_lossy()
|
|
||||||
.into_owned();
|
|
||||||
let json =
|
|
||||||
std::fs::read_to_string(&path).unwrap_or_else(|e| panic!("{name}: {e}"));
|
|
||||||
match command_census::capability_file(&name, &json) {
|
|
||||||
Ok(file) => files.push(file),
|
|
||||||
Err(problem) => fail(&[problem]),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
files.sort_by(|a, b| a.name.cmp(&b.name));
|
|
||||||
|
|
||||||
let problems = command_census::check(&commands, &files);
|
let problems = command_census::check(&commands, &files);
|
||||||
if !problems.is_empty() {
|
if !problems.is_empty() {
|
||||||
fail(&problems);
|
fail(
|
||||||
|
"capabilities do not match generate_handler!",
|
||||||
|
&problems,
|
||||||
|
"Every app command needs exactly one bare `allow-<command-with-dashes>` grant: \
|
||||||
|
`viewer_*` commands in capabilities/file-viewer.json, everything else in \
|
||||||
|
capabilities/default.json. See src/command_census.rs.",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
prune_permissions(&commands);
|
prune_permissions(&commands);
|
||||||
@@ -92,7 +102,8 @@ fn main() {
|
|||||||
/// tauri-build also emits `rerun-if-changed=permissions`, so regenerating everything would
|
/// tauri-build also emits `rerun-if-changed=permissions`, so regenerating everything would
|
||||||
/// touch every mtime and re-run this script — and recompile the crate — on every cargo
|
/// touch every mtime and re-run this script — and recompile the crate — on every cargo
|
||||||
/// invocation. Anything else under `permissions/` is a hand-written grant the census cannot
|
/// invocation. Anything else under `permissions/` is a hand-written grant the census cannot
|
||||||
/// see, so it is refused.
|
/// see, so it is refused — except OS/editor junk (`.DS_Store`, swap files), which tauri never
|
||||||
|
/// loads and which is skipped (see `command_census::is_os_junk`).
|
||||||
fn prune_permissions(commands: &[String]) {
|
fn prune_permissions(commands: &[String]) {
|
||||||
let root = Path::new("permissions");
|
let root = Path::new("permissions");
|
||||||
let Ok(entries) = std::fs::read_dir(root) else {
|
let Ok(entries) = std::fs::read_dir(root) else {
|
||||||
@@ -100,6 +111,10 @@ fn prune_permissions(commands: &[String]) {
|
|||||||
};
|
};
|
||||||
for entry in entries {
|
for entry in entries {
|
||||||
let path = entry.expect("readable entry in permissions/").path();
|
let path = entry.expect("readable entry in permissions/").path();
|
||||||
|
if path.is_file() && command_census::is_os_junk(&file_name(&path)) {
|
||||||
|
// .DS_Store and friends: tauri never loads them, so they cannot grant anything.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if path.file_name().is_some_and(|n| n == "autogenerated") && path.is_dir() {
|
if path.file_name().is_some_and(|n| n == "autogenerated") && path.is_dir() {
|
||||||
for file in std::fs::read_dir(&path).expect("readable permissions/autogenerated") {
|
for file in std::fs::read_dir(&path).expect("readable permissions/autogenerated") {
|
||||||
let file = file.expect("readable entry").path();
|
let file = file.expect("readable entry").path();
|
||||||
@@ -112,11 +127,82 @@ fn prune_permissions(commands: &[String]) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
fail(&[format!(
|
fail(
|
||||||
"{} is not generated by build.rs; hand-written permissions are not allowed \
|
"hand-written permission",
|
||||||
(every grant is a bare allow-* string in a capability file)",
|
&[format!(
|
||||||
path.display()
|
"{} is not generated by build.rs; hand-written permissions are not allowed \
|
||||||
)]);
|
(every grant is a bare allow-* string in a capability file)",
|
||||||
|
path.display()
|
||||||
|
)],
|
||||||
|
"permissions/ holds only build.rs's autogenerated/ directory. Delete the entry; \
|
||||||
|
an app command is granted by listing allow-<command> in a capability file.",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Every capability tauri will load, read the way the census reads it — or the build stops.
|
||||||
|
/// tauri-build loads `capabilities/**/*.{json,toml,json5}`; the census reads only top-level
|
||||||
|
/// `*.json`, so anything else tauri could load is refused rather than granted unchecked.
|
||||||
|
fn read_capabilities() -> Vec<command_census::CapabilityFile> {
|
||||||
|
let mut files = Vec::new();
|
||||||
|
let mut stray = Vec::new();
|
||||||
|
let mut invalid = Vec::new();
|
||||||
|
for entry in std::fs::read_dir("capabilities").expect("capabilities/ must exist") {
|
||||||
|
let path = entry.expect("readable entry in capabilities/").path();
|
||||||
|
let name = file_name(&path);
|
||||||
|
let is_file = path.is_file();
|
||||||
|
if is_file && command_census::is_os_junk(&name) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if let Some(problem) = command_census::stray_capability_entry(&name, is_file) {
|
||||||
|
stray.push(problem);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let json = std::fs::read_to_string(&path).unwrap_or_else(|e| panic!("{name}: {e}"));
|
||||||
|
match command_census::capability_file(&name, &json) {
|
||||||
|
Ok(file) => files.push(file),
|
||||||
|
Err(problem) => invalid.push(problem),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stray.sort();
|
||||||
|
if !stray.is_empty() {
|
||||||
|
fail("stray entry in capabilities/", &stray, LAYOUT_HINT);
|
||||||
|
}
|
||||||
|
invalid.sort();
|
||||||
|
if !invalid.is_empty() {
|
||||||
|
fail("invalid capability file", &invalid, LAYOUT_HINT);
|
||||||
|
}
|
||||||
|
files.sort_by(|a, b| a.name.cmp(&b.name));
|
||||||
|
files
|
||||||
|
}
|
||||||
|
|
||||||
|
/// tauri also takes capabilities inline from `app.security.capabilities` in any of its config
|
||||||
|
/// files, or from the `TAURI_CONFIG` JSON that tauri-build merges over them. The census cannot
|
||||||
|
/// see those, so they are refused; so is a config in a format it cannot read (JSON5, TOML).
|
||||||
|
fn check_tauri_config() {
|
||||||
|
let mut problems = Vec::new();
|
||||||
|
for entry in std::fs::read_dir(".").expect("readable src-tauri/") {
|
||||||
|
let path = entry.expect("readable entry in src-tauri/").path();
|
||||||
|
let name = file_name(&path);
|
||||||
|
match command_census::tauri_config_file(&name) {
|
||||||
|
None => {}
|
||||||
|
Some(false) => problems.push(format!(
|
||||||
|
"{name}: the census reads JSON tauri configs only; a JSON5/TOML config could \
|
||||||
|
declare capabilities it cannot see"
|
||||||
|
)),
|
||||||
|
Some(true) => {
|
||||||
|
let json =
|
||||||
|
std::fs::read_to_string(&path).unwrap_or_else(|e| panic!("{name}: {e}"));
|
||||||
|
problems.extend(command_census::tauri_config_problem(&name, &json));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Ok(json) = std::env::var("TAURI_CONFIG") {
|
||||||
|
problems.extend(command_census::tauri_config_problem("TAURI_CONFIG", &json));
|
||||||
|
}
|
||||||
|
problems.sort();
|
||||||
|
if !problems.is_empty() {
|
||||||
|
fail("capabilities declared outside capabilities/", &problems, LAYOUT_HINT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -75,6 +75,16 @@ pub struct CapabilityFile {
|
|||||||
pub fn capability_file(name: &str, json: &str) -> Result<CapabilityFile, String> {
|
pub fn capability_file(name: &str, json: &str) -> Result<CapabilityFile, String> {
|
||||||
let value: serde_json::Value =
|
let value: serde_json::Value =
|
||||||
serde_json::from_str(json).map_err(|e| format!("{name}: not valid JSON: {e}"))?;
|
serde_json::from_str(json).map_err(|e| format!("{name}: not valid JSON: {e}"))?;
|
||||||
|
// `webviews` would extend the grants to webviews by label (the browser-view pop-out is
|
||||||
|
// meant to be in no capability), and `remote` would extend them to a remote origin. The
|
||||||
|
// census reasons about `windows` only, so either key is refused rather than half-checked.
|
||||||
|
for key in ["webviews", "remote"] {
|
||||||
|
if value.get(key).is_some() {
|
||||||
|
return Err(format!(
|
||||||
|
"{name}: `{key}` is not allowed; capabilities here are scoped by `windows` only"
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
let windows = value["windows"]
|
let windows = value["windows"]
|
||||||
.as_array()
|
.as_array()
|
||||||
.ok_or_else(|| format!("{name}: `windows` must be an array"))?
|
.ok_or_else(|| format!("{name}: `windows` must be an array"))?
|
||||||
@@ -105,6 +115,79 @@ pub fn capability_file(name: &str, json: &str) -> Result<CapabilityFile, String>
|
|||||||
Ok(CapabilityFile { name: name.to_string(), windows, bare })
|
Ok(CapabilityFile { name: name.to_string(), windows, bare })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Why an entry directly under `capabilities/` cannot be a capability the census reads, or
|
||||||
|
/// `None` if it is one (a top-level `*.json` file). tauri-build loads `capabilities/**/*` with
|
||||||
|
/// the extensions `json`, `toml` and (with a feature) `json5`, subdirectories included; the
|
||||||
|
/// census reads only top-level JSON, so anything else tauri might load is refused rather than
|
||||||
|
/// left for tauri to grant from unchecked. OS and editor junk, which tauri never loads, is the
|
||||||
|
/// caller's to skip first (see [`is_os_junk`]).
|
||||||
|
pub fn stray_capability_entry(name: &str, is_file: bool) -> Option<String> {
|
||||||
|
if !is_file {
|
||||||
|
return Some(format!(
|
||||||
|
"capabilities/{name} is not a regular file; tauri loads capabilities from \
|
||||||
|
subdirectories too, so every capability must be a top-level capabilities/*.json"
|
||||||
|
));
|
||||||
|
}
|
||||||
|
if name.ends_with(".json") {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
Some(format!(
|
||||||
|
"capabilities/{name} is not a .json file; tauri may load it (it reads .toml and .json5 \
|
||||||
|
too) but the census cannot check it, so every capability must be a top-level \
|
||||||
|
capabilities/*.json"
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Files the OS or an editor drops next to real ones (`.DS_Store`, `Thumbs.db`, `desktop.ini`,
|
||||||
|
/// Vim swap files, `name~` backups). tauri-build loads only `json`/`toml`/`json5` from
|
||||||
|
/// `capabilities/` and `permissions/`, so a junk name with one of those extensions (an Emacs
|
||||||
|
/// `.#default.json` lock, a macOS `._default.json`) is *not* junk: tauri would try to load it,
|
||||||
|
/// and the caller must refuse it.
|
||||||
|
pub fn is_os_junk(name: &str) -> bool {
|
||||||
|
let loadable = [".json", ".json5", ".toml"].iter().any(|e| name.ends_with(e));
|
||||||
|
!loadable
|
||||||
|
&& (matches!(name, ".DS_Store" | "Thumbs.db" | "desktop.ini")
|
||||||
|
|| name.ends_with(".swp")
|
||||||
|
|| name.ends_with(".swo")
|
||||||
|
|| name.ends_with('~'))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Which files next to `Cargo.toml` tauri reads as its config: `tauri.conf.json[5]`,
|
||||||
|
/// `Tauri.toml` and the per-platform `tauri.<platform>.conf.json[5]` / `Tauri.<platform>.toml`
|
||||||
|
/// (tauri-utils `config/parse.rs`). `Some(true)` = JSON the census can read, `Some(false)` = a
|
||||||
|
/// format it cannot (JSON5/TOML), `None` = not a tauri config file.
|
||||||
|
pub fn tauri_config_file(name: &str) -> Option<bool> {
|
||||||
|
if name.starts_with("tauri.") && name.ends_with(".conf.json") {
|
||||||
|
Some(true)
|
||||||
|
} else if (name.starts_with("tauri.") && name.ends_with(".conf.json5"))
|
||||||
|
|| (name.starts_with("Tauri.") && name.ends_with(".toml"))
|
||||||
|
{
|
||||||
|
Some(false)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A problem with a tauri config (a `tauri*.conf.json` file, or the `TAURI_CONFIG` JSON that
|
||||||
|
/// tauri-build merges over it), or `None`. `app.security.capabilities` is refused whenever it
|
||||||
|
/// is non-empty: an inline object is a capability the census never sees, and a list of
|
||||||
|
/// identifiers switches every *other* capability file off, which the census also assumes is
|
||||||
|
/// not happening.
|
||||||
|
pub fn tauri_config_problem(name: &str, json: &str) -> Option<String> {
|
||||||
|
let value: serde_json::Value = match serde_json::from_str(json) {
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(e) => return Some(format!("{name}: not valid JSON: {e}")),
|
||||||
|
};
|
||||||
|
match value.pointer("/app/security/capabilities") {
|
||||||
|
None | Some(serde_json::Value::Null) => None,
|
||||||
|
Some(serde_json::Value::Array(a)) if a.is_empty() => None,
|
||||||
|
Some(_) => Some(format!(
|
||||||
|
"{name}: app.security.capabilities is not allowed; every capability lives in a \
|
||||||
|
top-level capabilities/*.json file, where the census checks it"
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Everything that must hold between the handler list and the capability files. Returns every
|
/// Everything that must hold between the handler list and the capability files. Returns every
|
||||||
/// violation rather than the first, so a batch of forgotten grants is one build failure; an
|
/// violation rather than the first, so a batch of forgotten grants is one build failure; an
|
||||||
/// empty vector is a pass.
|
/// empty vector is a pass.
|
||||||
@@ -324,6 +407,70 @@ mod tests {
|
|||||||
assert!(capability_file("x.json", "not json").unwrap_err().contains("x.json"));
|
assert!(capability_file("x.json", "not json").unwrap_err().contains("x.json"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn webviews_and_remote_keys_are_refused() {
|
||||||
|
let with = |extra: &str| {
|
||||||
|
format!(r#"{{"windows": ["main"], {extra}, "permissions": ["allow-check-docker"]}}"#)
|
||||||
|
};
|
||||||
|
let err = capability_file("d.json", &with(r#""webviews": ["browser-view-*"]"#)).unwrap_err();
|
||||||
|
assert!(err.contains("d.json") && err.contains("`webviews`"), "{err}");
|
||||||
|
let err = capability_file("d.json", &with(r#""remote": {"urls": ["https://*"]}"#)).unwrap_err();
|
||||||
|
assert!(err.contains("`remote`"), "{err}");
|
||||||
|
// Present-but-empty is still refused: the key itself is the widening surface.
|
||||||
|
assert!(capability_file("d.json", &with(r#""webviews": []"#)).is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn only_top_level_json_files_are_capabilities() {
|
||||||
|
assert_eq!(stray_capability_entry("default.json", true), None);
|
||||||
|
for name in ["extra.toml", "extra.json5", "notes.txt", ".DS_Store"] {
|
||||||
|
let err = stray_capability_entry(name, true).expect(name);
|
||||||
|
assert!(err.contains(name) && err.contains("not a .json file"), "{err}");
|
||||||
|
}
|
||||||
|
let err = stray_capability_entry("sub", false).unwrap();
|
||||||
|
assert!(err.contains("capabilities/sub") && err.contains("not a regular file"), "{err}");
|
||||||
|
// A directory named like a capability is still a directory.
|
||||||
|
assert!(stray_capability_entry("x.json", false).is_some());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn os_junk_is_recognised_but_never_something_tauri_would_load() {
|
||||||
|
for junk in [".DS_Store", "Thumbs.db", "desktop.ini", ".default.json.swp", ".x.swo", "default.json~"] {
|
||||||
|
assert!(is_os_junk(junk), "{junk}");
|
||||||
|
}
|
||||||
|
for real in ["default.json", "x.toml", "x.json5", ".#default.json", "._default.json", "notes.txt", "extra"] {
|
||||||
|
assert!(!is_os_junk(real), "{real}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn tauri_config_files_are_found_by_name_and_format() {
|
||||||
|
assert_eq!(tauri_config_file("tauri.conf.json"), Some(true));
|
||||||
|
assert_eq!(tauri_config_file("tauri.linux.conf.json"), Some(true));
|
||||||
|
assert_eq!(tauri_config_file("tauri.conf.json5"), Some(false));
|
||||||
|
assert_eq!(tauri_config_file("tauri.windows.conf.json5"), Some(false));
|
||||||
|
assert_eq!(tauri_config_file("Tauri.toml"), Some(false));
|
||||||
|
assert_eq!(tauri_config_file("Tauri.macos.toml"), Some(false));
|
||||||
|
assert_eq!(tauri_config_file("Cargo.toml"), None);
|
||||||
|
assert_eq!(tauri_config_file("build.rs"), None);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn inline_capabilities_in_the_tauri_config_are_refused() {
|
||||||
|
let ok = r#"{"app": {"security": {"csp": "default-src 'self'"}}}"#;
|
||||||
|
assert_eq!(tauri_config_problem("tauri.conf.json", ok), None);
|
||||||
|
assert_eq!(tauri_config_problem("t", r#"{"app": {"security": {"capabilities": []}}}"#), None);
|
||||||
|
assert_eq!(tauri_config_problem("t", r#"{"build": {"beforeBuildCommand": ""}}"#), None);
|
||||||
|
let inline = r#"{"app": {"security": {"capabilities": [
|
||||||
|
{"identifier": "x", "windows": ["file-viewer-*"], "permissions": ["allow-read-container-file"]}
|
||||||
|
]}}}"#;
|
||||||
|
let err = tauri_config_problem("tauri.conf.json", inline).unwrap();
|
||||||
|
assert!(err.contains("tauri.conf.json") && err.contains("app.security.capabilities"), "{err}");
|
||||||
|
let by_name = r#"{"app": {"security": {"capabilities": ["default"]}}}"#;
|
||||||
|
assert!(tauri_config_problem("TAURI_CONFIG", by_name).unwrap().contains("TAURI_CONFIG"));
|
||||||
|
assert!(tauri_config_problem("t", "{").unwrap().contains("not valid JSON"));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn a_correct_census_has_no_problems() {
|
fn a_correct_census_has_no_problems() {
|
||||||
assert_eq!(check(&cmds(THREE), &good_files()), Vec::<String>::new());
|
assert_eq!(check(&cmds(THREE), &good_files()), Vec::<String>::new());
|
||||||
|
|||||||
Reference in New Issue
Block a user