Compare commits

...

5 Commits

Author SHA1 Message Date
zach
fad7eb4454 fix(manifest): derive JsonSchema for LocalPath 2025-06-25 11:34:55 -07:00
zach
37e0e2fed4 fix: invert allowed_paths map to allow for multiple virtual paths to point to the same local path 2025-06-23 10:08:45 -07:00
zach
5b94feb7ec fix: clippy 2025-06-23 09:49:59 -07:00
zach
6146d2f47c cleanup: clippy 2025-06-23 09:49:59 -07:00
zach
424e6c328a cleanup: add extism_manifest::LocalPath for specifying allowed paths 2025-06-23 09:49:59 -07:00
3 changed files with 132 additions and 9 deletions

View File

@@ -1,6 +1,10 @@
use std::collections::BTreeMap;
use std::path::{Path, PathBuf};
mod local_path;
pub use local_path::LocalPath;
#[deprecated]
pub type ManifestMemory = MemoryOptions;
@@ -279,7 +283,7 @@ pub struct Manifest {
/// the path on disk to the path it should be available inside the plugin.
/// For example, `".": "/tmp"` would mount the current directory as `/tmp` inside the module
#[serde(default)]
pub allowed_paths: Option<BTreeMap<String, PathBuf>>,
pub allowed_paths: Option<BTreeMap<PathBuf, LocalPath>>,
/// The plugin timeout in milliseconds
#[serde(default)]
@@ -337,15 +341,15 @@ impl Manifest {
}
/// Add a path to `allowed_paths`
pub fn with_allowed_path(mut self, src: String, dest: impl AsRef<Path>) -> Self {
pub fn with_allowed_path(mut self, src: impl Into<LocalPath>, dest: impl AsRef<Path>) -> Self {
let dest = dest.as_ref().to_path_buf();
match &mut self.allowed_paths {
Some(p) => {
p.insert(src, dest);
p.insert(dest, src.into());
}
None => {
let mut p = BTreeMap::new();
p.insert(src, dest);
p.insert(dest, src.into());
self.allowed_paths = Some(p);
}
}
@@ -354,8 +358,8 @@ impl Manifest {
}
/// Set `allowed_paths`
pub fn with_allowed_paths(mut self, paths: impl Iterator<Item = (String, PathBuf)>) -> Self {
self.allowed_paths = Some(paths.collect());
pub fn with_allowed_paths(mut self, paths: impl Iterator<Item = (LocalPath, PathBuf)>) -> Self {
self.allowed_paths = Some(paths.map(|(local, wasm)| (wasm, local)).collect());
self
}

119
manifest/src/local_path.rs Normal file
View File

@@ -0,0 +1,119 @@
use std::path::{Path, PathBuf};
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
#[cfg_attr(feature = "json_schema", derive(schemars::JsonSchema))]
pub enum LocalPath {
ReadOnly(PathBuf),
ReadWrite(PathBuf),
}
impl LocalPath {
pub fn as_path(&self) -> &Path {
match self {
LocalPath::ReadOnly(p) => p.as_path(),
LocalPath::ReadWrite(p) => p.as_path(),
}
}
}
impl From<&str> for LocalPath {
fn from(value: &str) -> Self {
if let Some(s) = value.strip_prefix("ro:") {
LocalPath::ReadOnly(PathBuf::from(s))
} else {
LocalPath::ReadWrite(PathBuf::from(value))
}
}
}
impl From<String> for LocalPath {
fn from(value: String) -> Self {
LocalPath::from(value.as_str())
}
}
impl From<PathBuf> for LocalPath {
fn from(value: PathBuf) -> Self {
LocalPath::ReadWrite(value)
}
}
impl From<&Path> for LocalPath {
fn from(value: &Path) -> Self {
LocalPath::ReadWrite(value.to_path_buf())
}
}
impl serde::Serialize for LocalPath {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
match self {
LocalPath::ReadOnly(path) => {
let s = match path.to_str() {
Some(s) => s,
None => {
return Err(serde::ser::Error::custom(
"Path contains invalid UTF-8 characters",
))
}
};
format!("ro:{s}").serialize(serializer)
}
LocalPath::ReadWrite(path) => path.serialize(serializer),
}
}
}
struct LocalPathVisitor;
impl serde::de::Visitor<'_> for LocalPathVisitor {
type Value = LocalPath;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("path string")
}
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Ok(From::from(v))
}
fn visit_string<E>(self, v: String) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Ok(From::from(v))
}
fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
std::str::from_utf8(v)
.map(From::from)
.map_err(|_| serde::de::Error::invalid_value(serde::de::Unexpected::Bytes(v), &self))
}
fn visit_byte_buf<E>(self, v: Vec<u8>) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
String::from_utf8(v).map(From::from).map_err(|e| {
serde::de::Error::invalid_value(serde::de::Unexpected::Bytes(&e.into_bytes()), &self)
})
}
}
impl<'de> serde::Deserialize<'de> for LocalPath {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::de::Deserializer<'de>,
{
deserializer.deserialize_string(LocalPathVisitor)
}
}

View File

@@ -352,9 +352,9 @@ impl CurrentPlugin {
if let Some(a) = &manifest.allowed_paths {
for (k, v) in a.iter() {
let readonly = k.starts_with("ro:");
let readonly = matches!(v, extism_manifest::LocalPath::ReadOnly(_));
let dir_path = if readonly { &k[3..] } else { k };
let dir_path = v.as_path();
let dir = wasi_common::sync::dir::Dir::from_cap_std(
wasi_common::sync::Dir::open_ambient_dir(dir_path, auth)?,
@@ -366,7 +366,7 @@ impl CurrentPlugin {
Box::new(dir)
};
ctx.push_preopened_dir(file, v)?;
ctx.push_preopened_dir(file, k)?;
}
}