plugin-api: Rework filesystem permissions and move data folders. (#2643)

This commit is contained in:
Demetrius Kanios
2026-07-29 00:36:22 -07:00
committed by GitHub
parent 8db9c013b1
commit ef325c459e
6 changed files with 51 additions and 84 deletions

View File

@@ -34,16 +34,14 @@ pub const NETWORK_OUTBOUND: &str = "network.outbound";
/// This is separate from `network.outbound`. This allows the use of `wasi:http`; the other allows the more powerful `wasi:sockets`.
pub const HTTP_OUTBOUND: &str = "http.outbound";
/// Allows the plugin to read files from the server's file system outside of its data folder.
pub const FS_READ: &str = "fs.read";
/// Allows the plugin to write files to the server's file system outside of its data folder.
pub const FS_WRITE: &str = "fs.write";
/// Allows the plugin to read files within its own data folder (`plugins/<name>`).
/// Allows the plugin to read files within its own data folder (`plugins/data/<name>`).
pub const FS_READ_DATA: &str = "fs.read.data";
/// Allows the plugin to write files within its own data folder (`plugins/<name>`).
/// Allows the plugin to write files within its own data folder (`plugins/data/<name>`).
///
/// Note that even without `FS_READ_DATA`, this will allow the plugin to
/// inspect (e.g. list) the contents of the directory. But it will block
/// reading any file's contents.
pub const FS_WRITE_DATA: &str = "fs.write.data";
/// Allows the plugin to read all environment variables.

View File

@@ -80,7 +80,7 @@ impl Context {
/// A string representing the path to the data folder.
#[must_use]
pub fn get_data_folder(&self) -> PathBuf {
let path = Path::new("plugins").join(&self.metadata.name);
let path = Path::new("plugins").join("data").join(&self.metadata.name);
if !path.exists() {
fs::create_dir_all(&path).unwrap();
}

View File

@@ -2,7 +2,7 @@ use std::{fs, path::Path, sync::Arc};
use thiserror::Error;
use tokio::sync::Mutex;
use wasmtime::{Cache, CacheConfig, Engine, Store, component::Component, component::Linker};
use wasmtime_wasi::{WasiCtxBuilder, sockets::SocketAddrUse};
use wasmtime_wasi::{DirPerms, FilePerms, WasiCtxBuilder, sockets::SocketAddrUse};
use crate::plugin::{
Context, PluginMetadata, loader::wasm::wasm_host::state::PluginHostState, permissions,
@@ -143,7 +143,6 @@ fn load_component(
}
impl WasmPlugin {
#[expect(clippy::too_many_lines)]
pub async fn on_load(
&self,
context: Arc<Context>,
@@ -221,48 +220,29 @@ impl WasmPlugin {
}
}
let data_folder = context.get_data_folder();
let preopen_path =
if has_permission(permissions::FS_READ) || has_permission(permissions::FS_WRITE) {
Path::new(".")
} else {
data_folder.as_path()
};
// Determine permissions for the preopened directory
let (dir_perms, file_perms) = if has_permission(permissions::FS_WRITE) {
(
wasmtime_wasi::DirPerms::all(),
wasmtime_wasi::FilePerms::all(),
)
} else if has_permission(permissions::FS_READ) {
(
wasmtime_wasi::DirPerms::READ,
wasmtime_wasi::FilePerms::READ,
)
} else {
// Scoped to data folder
let can_write = has_permission(permissions::FS_WRITE_DATA);
if can_write {
(
wasmtime_wasi::DirPerms::all(),
wasmtime_wasi::FilePerms::all(),
)
} else {
// Default to READ if no write permission is given for data folder
// (Plugins should at least be able to read their own config)
(
wasmtime_wasi::DirPerms::READ,
wasmtime_wasi::FilePerms::READ,
)
}
};
builder.preopened_dir(
preopen_path,
preopen_path.to_string_lossy(),
dir_perms,
file_perms,
context.get_data_folder(),
"data",
if has_permission(permissions::FS_READ_DATA)
|| has_permission(permissions::FS_WRITE_DATA)
{
DirPerms::READ
} else {
DirPerms::empty()
} | if has_permission(permissions::FS_WRITE_DATA) {
DirPerms::MUTATE
} else {
DirPerms::empty()
},
if has_permission(permissions::FS_READ_DATA) {
FilePerms::READ
} else {
FilePerms::empty()
} | if has_permission(permissions::FS_WRITE_DATA) {
FilePerms::WRITE
} else {
FilePerms::empty()
},
)?;
if has_permission(permissions::HTTP_OUTBOUND) {

View File

@@ -419,13 +419,8 @@ impl pumpkin::plugin::context::HostContext for PluginHostState {
.await)
}
async fn get_data_folder(&mut self, context: Resource<Context>) -> wasmtime::Result<String> {
Ok(self
.get_context(&context)?
.provider
.get_data_folder()
.to_string_lossy()
.into_owned())
async fn get_data_folder(&mut self, _context: Resource<Context>) -> wasmtime::Result<String> {
Ok("data".to_string())
}
async fn get_server(

View File

@@ -486,15 +486,13 @@ impl PluginManager {
metadata.version.green()
);
for permission in &metadata.permissions {
if let Some(description) = permissions::get_permission_description(permission) {
println!(
" - {}: {}",
permission.yellow().bold(),
description.italic()
);
} else {
println!(" - {}", permission.yellow().bold());
}
println!(
" - {}: {}",
permission.yellow().bold(),
permissions::get_permission_description(permission)
.unwrap_or("<unknown permission>")
.italic()
);
}
let prompt = format!(

View File

@@ -38,17 +38,14 @@ pub const NETWORK_OUTBOUND: &str = "network.outbound";
/// This is separate from `network.outbound`. This allows the use of `wasi:http`; the other allows the more powerful `wasi:sockets`.
pub const HTTP_OUTBOUND: &str = "http.outbound";
/// Allows the plugin to read files from the server's file system outside of its data folder.
pub const FS_READ: &str = "fs.read";
/// Allows the plugin to write files to the server's file system outside of its data folder.
pub const FS_WRITE: &str = "fs.write";
/// Allows the plugin to read files within its own data folder (`plugins/<name>`).
/// This is granted by default if any other FS permission is not specified, but can be explicitly requested.
/// Allows the plugin to read files within its own data folder (`plugins/data/<name>`).
pub const FS_READ_DATA: &str = "fs.read.data";
/// Allows the plugin to write files within its own data folder (`plugins/<name>`).
/// Allows the plugin to write files within its own data folder (`plugins/data/<name>`).
///
/// Note that even without `FS_READ_DATA`, this will allow the plugin to
/// inspect (e.g. list) the contents of the directory. But it will block
/// reading any file's contents.
pub const FS_WRITE_DATA: &str = "fs.write.data";
/// Allows the plugin to read all environment variables.
@@ -98,14 +95,13 @@ pub fn get_permission_description(permission: &str) -> Option<&'static str> {
HTTP_OUTBOUND => {
Some("Allows the plugin to make outbound HTTP requests (through `wasi:http`)")
}
FS_READ => Some(
"Allows the plugin to read files from the server's file system outside of its data folder.",
),
FS_WRITE => Some(
"Allows the plugin to write files to the server's file system outside of its data folder.",
),
FS_READ_DATA => Some("Allows the plugin to read files within its own data folder."),
FS_WRITE_DATA => Some("Allows the plugin to write files within its own data folder."),
FS_WRITE_DATA => Some(
"\
Allows the plugin to write files within its own data folder. \
Even without `fs.read.data`, this will allow the plugin to list directory contents \
(but not read the contents of the discovered files).",
),
SYS_ENV => Some("Allows the plugin to read all environment variables."),
SYS_INFO => Some("Allows the plugin to read system information (CPU, Memory, OS)."),
SYS_INFO_CPU => Some("Allows the plugin to read CPU information."),