Files
vectorcraft/crates/plugins/tests/grant_sites.rs
T
dotCooCoo cde367b889 Plug-ins: only an approval records a grant, and installs need one
registry::grant takes a GrantToken, which the engine makes after a
person's approval and when it loads grants.json. registry::install,
install_bytes and folder loads refuse a module that lists permissions no
recorded grant gives this very module (Error::NeedsApproval), and leave
the plug-in under its id and the revision as they were. A folder load's
approve callback only narrows what installs. A module without
permissions, or with an empty list, installs as before.

A folder load in a session with a plug-in store records the store's
grant for each module it approves, so those modules still load.

docs/plugins.md says what the approval does not protect against: native
code linked into the app, and another program running as the same user,
which can edit the Plug-ins folder and its unsigned grants.json.
2026-11-11 17:35:06 -08:00

83 lines
3.9 KiB
Rust

//! The shipped code that records a plug-in grant: only the engine's install approval and its load
//! of the plug-in folder's grants file make a `GrantToken` and call `registry::grant`. A new call
//! site fails this test until it is added to the list. It checks the source, not what native code
//! can do at run time.
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
use std::collections::BTreeMap;
use std::path::{Path, PathBuf};
/// `(text, file, how many times)` for every place shipped code may name it.
const EXPECTED: &[(&str, &str, usize)] = &[
("GrantToken::after_engine_approval", "crates/engine/src/cmd/plugin.rs", 1),
("GrantToken::from_engine_grants_file", "crates/engine/src/store.rs", 2),
("registry::grant", "crates/engine/src/cmd/plugin.rs", 1),
("registry::grant", "crates/engine/src/store.rs", 2),
];
/// Every `.rs` file under `dir`, without test files and folders.
fn sources(dir: &Path, out: &mut Vec<PathBuf>) {
let Ok(entries) = std::fs::read_dir(dir) else { return };
for entry in entries.flatten() {
let path = entry.path();
let name = entry.file_name().to_string_lossy().to_string();
if path.is_dir() {
if !matches!(name.as_str(), "tests" | "benches" | "examples" | "target" | "fuzz") {
sources(&path, out);
}
} else if name.ends_with(".rs") && name != "tests.rs" && !name.starts_with("tests_") && !name.ends_with("_tests.rs") {
out.push(path);
}
}
}
/// The lines of `text` that ship: not comments, and nothing from a top-level `#[cfg(test)] mod x {`
/// on.
fn shipped(text: &str) -> Vec<&str> {
let lines: Vec<&str> = text.lines().collect();
let opens_test_module = |i: usize| {
lines[i] == "#[cfg(test)]"
&& lines[i + 1..].iter().find(|l| !l.trim().is_empty()).is_some_and(|l| l.starts_with("mod ") && l.trim_end().ends_with('{'))
};
let end = (0..lines.len()).find(|&i| opens_test_module(i)).unwrap_or(lines.len());
lines[..end].iter().copied().filter(|l| !l.trim_start().starts_with("//")).collect()
}
/// How many times `name` occurs in `line` as a whole path (`registry::grant`, not `registry::granted`).
fn count(line: &str, name: &str) -> usize {
line.match_indices(name).filter(|(at, _)| !line[at + name.len()..].starts_with(|c: char| c.is_alphanumeric() || c == '_')).count()
}
#[test]
fn grant_token_call_sites_are_limited() {
let root = Path::new(env!("CARGO_MANIFEST_DIR")).join("../..");
let mut files = Vec::new();
for top in ["crates", "apps", "xtask"] {
sources(&root.join(top), &mut files);
}
assert!(files.len() > 100, "found only {} source files under {}", files.len(), root.display());
let mut found: BTreeMap<(&str, String), usize> = BTreeMap::new();
for file in &files {
let text = std::fs::read_to_string(file).unwrap();
let rel = file.strip_prefix(&root).unwrap().to_string_lossy().replace('\\', "/");
for line in shipped(&text) {
for name in ["GrantToken::after_engine_approval", "GrantToken::from_engine_grants_file", "registry::grant"] {
let n = count(line, name);
if n > 0 {
*found.entry((name, rel.clone())).or_default() += n;
}
}
}
}
let expected: BTreeMap<(&str, String), usize> = EXPECTED.iter().map(|&(name, file, n)| ((name, file.to_string()), n)).collect();
assert_eq!(found, expected, "shipped code that makes a grant token or records a grant");
}
/// The audit's own filters: a test module and comments are left out, a longer name isn't counted.
#[test]
fn the_audit_skips_test_code_and_comments() {
let text = "fn a() { registry::grant(t); }\n// registry::grant(t)\nfn b() { registry::granted(id); }\n\n#[cfg(test)]\n\nmod tests {\n registry::grant(t);\n}\n";
let lines = shipped(text);
assert_eq!(lines.iter().map(|l| count(l, "registry::grant")).sum::<usize>(), 1, "{lines:?}");
}